修复bug;提高沙盒稳定性;优化沙盒性能

This commit is contained in:
IceCola 2024-05-29 01:53:01 +08:00
parent 7a8a98424c
commit 03380d1d93
6 changed files with 385 additions and 145 deletions

View File

@ -1515,7 +1515,7 @@ export class Get {
* ``` * ```
* *
* @param {string} str * @param {string} str
* @returns * @returns {string}
*/ */
pureFunctionStr(str) { pureFunctionStr(str) {
str = str.trim(); str = str.trim();
@ -1551,7 +1551,13 @@ export class Get {
if (func._filter_args) { if (func._filter_args) {
return "_noname_func:" + JSON.stringify(get.stringifiedResult(func._filter_args, 3)); return "_noname_func:" + JSON.stringify(get.stringifiedResult(func._filter_args, 3));
} }
const str = func.toString(); const { Marshal, Sandbox } = security.importSandbox();
const domain = Marshal.getMarshalledDomain(func);
if (domain) {
const sandbox = Sandbox.from(domain);
if (sandbox && "client" in sandbox) throw new Error("不应该二次扩散远程代码");
}
const str = Marshal.decompileFunction(func);
// js内置的函数 // js内置的函数
if (/\{\s*\[native code\]\s*\}/.test(str)) return "_noname_func:function () {}"; if (/\{\s*\[native code\]\s*\}/.test(str)) return "_noname_func:function () {}";
return "_noname_func:" + get.pureFunctionStr(str); return "_noname_func:" + get.pureFunctionStr(str);
@ -4960,10 +4966,10 @@ function freezeSlot(obj, key) {
Reflect.defineProperty(obj, key, descriptor); Reflect.defineProperty(obj, key, descriptor);
} }
freezeSlot(Get, "isFunctionBody"); freezeSlot(Get.prototype, "isFunctionBody");
freezeSlot(Get, "pureFunctionStr"); freezeSlot(Get.prototype, "pureFunctionStr");
freezeSlot(Get, "funcInfoOL"); freezeSlot(Get.prototype, "funcInfoOL");
freezeSlot(Get, "infoFuncOL"); freezeSlot(Get.prototype, "infoFuncOL");
export let get = new Get(); export let get = new Get();
/** /**

View File

@ -8,8 +8,9 @@ import security from "../../util/security.js";
export class Client { export class Client {
/** /**
* @param {import('../index.js').NodeWS | InstanceType<typeof import('ws').WebSocket> | Client} ws * @param {import('../index.js').NodeWS | InstanceType<typeof import('ws').WebSocket> | Client} ws
* @param {boolean} temp
*/ */
constructor(ws, temp) { constructor(ws, temp = false) {
if (ws instanceof Client) throw new Error("Client cannot copy."); if (ws instanceof Client) throw new Error("Client cannot copy.");
this.ws = ws; this.ws = ws;
/** /**
@ -19,8 +20,21 @@ export class Client {
this.id = ws.wsid || get.id(); this.id = ws.wsid || get.id();
this.closed = false; this.closed = false;
if (!temp) if (!temp) {
this.sandbox = security.createSandbox(); this.sandbox = security.createSandbox();
if (this.sandbox) {
Reflect.defineProperty(this, "sandbox", {
value: this.sandbox,
writable: false,
configurable: false,
});
Reflect.defineProperty(this.sandbox, "client", {
value: this,
writable: false,
configurable: false,
});
}
}
} }
send() { send() {
if (this.closed) return this; if (this.closed) return this;

View File

@ -164,8 +164,9 @@ export class Library {
* } } * } }
*/ */
node; node;
// 谁写的值类型是string这也太离谱了喵
/** /**
* @type { { [key: string]: string } } * @type { { [key: string]: Player } }
*/ */
playerOL; playerOL;
/** /**
@ -9561,14 +9562,13 @@ export class Library {
if (!Array.isArray(message) || typeof lib.message.client[message[0]] !== "function") { if (!Array.isArray(message) || typeof lib.message.client[message[0]] !== "function") {
throw "err"; throw "err";
} }
if (!game.sandbox) game.sandbox = security.createSandbox(); if (game.sandbox) security.enterSandbox(game.sandbox);
security.enterSandbox(game.sandbox);
try { try {
for (var i = 1; i < message.length; i++) { for (var i = 1; i < message.length; i++) {
message[i] = get.parsedResult(message[i]); message[i] = get.parsedResult(message[i]);
} }
} finally { } finally {
security.exitSandbox(); if (game.sandbox) security.exitSandbox();
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);
@ -12011,7 +12011,9 @@ export class Library {
cardPile = {}; cardPile = {};
message = { message = {
server: { server: {
/** @this { any } */ /**
* @this {import("./element/client.js").Client}
*/
init(version, config, banned_info) { init(version, config, banned_info) {
if (lib.node.banned.includes(banned_info)) { if (lib.node.banned.includes(banned_info)) {
this.send("denied", "banned"); this.send("denied", "banned");
@ -12030,9 +12032,14 @@ export class Library {
lib.node.clients.remove(this); lib.node.clients.remove(this);
this.closed = true; this.closed = true;
} else if (!_status.waitingForPlayer) { } else if (!_status.waitingForPlayer) {
if (game.phaseNumber && lib.configOL.observe) { if (!config.nickname) {
this.send("denied", "banned");
lib.node.clients.remove(this);
this.closed = true;
} else if (game.phaseNumber && lib.configOL.observe) {
lib.node.observing.push(this); lib.node.observing.push(this);
this.send("reinit", lib.configOL, get.arenaState(), game.getState ? game.getState() : {}, game.ip, game.players[0].playerid, null, _status.cardtag); this.send("reinit", lib.configOL, get.arenaState(), game.getState ? game.getState() : {}, game.ip, game.players[0].playerid, null, _status.cardtag);
game.broadcastAll(name => game.log("玩家 ", `#y${name}`, " 进入房间旁观"), config.nickname);
if (!ui.removeObserve) { if (!ui.removeObserve) {
ui.removeObserve = ui.create.system( ui.removeObserve = ui.create.system(
"移除旁观", "移除旁观",
@ -12077,37 +12084,53 @@ export class Library {
this.send("init", this.id, lib.configOL, game.ip, window.isNonameServer, game.roomId); this.send("init", this.id, lib.configOL, game.ip, window.isNonameServer, game.roomId);
} }
}, },
/** @this { any } */ /**
* @this {import("./element/client.js").Client}
*/
inited() { inited() {
this.inited = true; this.inited = true;
if (_status.waitingForPlayer) { if (_status.waitingForPlayer) {
game.updateWaiting(); game.updateWaiting();
} }
}, },
/** @this { any } */ /**
* @this {import("./element/client.js").Client}
*/
reinited() { reinited() {
this.inited = true; this.inited = true;
}, },
result: function (result) { /**
* @this {import("./element/client.js").Client}
*/
result(result) {
if (lib.node.observing.includes(this)) return; if (lib.node.observing.includes(this)) return;
var player = lib.playerOL[this.id]; var player = lib.playerOL[this.id];
if (player) { if (player) {
player.unwait(result); player.unwait(result);
} }
}, },
tempResult: function (result) { /**
* @this {import("./element/client.js").Client}
*/
tempResult(result) {
if (lib.node.observing.includes(this)) return; if (lib.node.observing.includes(this)) return;
var player = lib.playerOL[this.id]; var player = lib.playerOL[this.id];
if (player) { if (player) {
player.tempUnwait(result); player.tempUnwait(result);
} }
}, },
startGame: function () { /**
* @this {import("./element/client.js").Client}
*/
startGame() {
if (this.id == game.onlinezhu) { if (this.id == game.onlinezhu) {
game.resume(); game.resume();
} }
}, },
changeRoomConfig: function (config) { /**
* @this {import("./element/client.js").Client}
*/
changeRoomConfig(config) {
if (this.id == game.onlinezhu) { if (this.id == game.onlinezhu) {
game.broadcastAll(function (config) { game.broadcastAll(function (config) {
for (var i in config) { for (var i in config) {
@ -12134,7 +12157,10 @@ export class Library {
} }
} }
}, },
changeNumConfig: function (num, index, bool) { /**
* @this {import("./element/client.js").Client}
*/
changeNumConfig(num, index, bool) {
if (this.id == game.onlinezhu) { if (this.id == game.onlinezhu) {
lib.configOL.number = num; lib.configOL.number = num;
game.send("server", "config", lib.configOL); game.send("server", "config", lib.configOL);
@ -12148,14 +12174,20 @@ export class Library {
} }
} }
}, },
throwEmotion: function (target, emotion, rotate) { /**
* @this {import("./element/client.js").Client}
*/
throwEmotion(target, emotion, rotate) {
if (lib.node.observing.includes(this)) return; if (lib.node.observing.includes(this)) return;
var player = lib.playerOL[this.id]; var player = lib.playerOL[this.id];
if (player) { if (player) {
player.throwEmotion(target, emotion, rotate); player.throwEmotion(target, emotion, rotate);
} }
}, },
emotion: function (id, pack, emotion) { /**
* @this {import("./element/client.js").Client}
*/
emotion(id, pack, emotion) {
if (lib.node.observing.includes(this)) return; if (lib.node.observing.includes(this)) return;
var that = this; var that = this;
if ( if (
@ -12185,7 +12217,10 @@ export class Library {
} }
if (player) player.emotion(pack, emotion); if (player) player.emotion(pack, emotion);
}, },
chat: function (id, str) { /**
* @this {import("./element/client.js").Client}
*/
chat(id, str) {
if (lib.node.observing.includes(this)) return; if (lib.node.observing.includes(this)) return;
var that = this; var that = this;
if ( if (
@ -12215,8 +12250,18 @@ export class Library {
} }
if (player) player.chat(str); if (player) player.chat(str);
}, },
giveup: function (player) { /**
* ```plain
* 当客机向主机发送投降请求时的回调
* ```
*
* @this {import("./element/client.js").Client}
* @param {Player} player
*/
giveup(player) {
if (lib.node.observing.includes(this) || !player || !player._giveUp) return; if (lib.node.observing.includes(this) || !player || !player._giveUp) return;
var self = lib.playerOL[this.id];
if (self !== player) return; // 禁止让别人投降
_status.event.next.length = 0; _status.event.next.length = 0;
game game
.createEvent("giveup", false) .createEvent("giveup", false)
@ -12227,7 +12272,10 @@ export class Library {
player.die("nosource").includeOut = true; player.die("nosource").includeOut = true;
}).player = player; }).player = player;
}, },
auto: function () { /**
* @this {import("./element/client.js").Client}
*/
auto() {
if (lib.node.observing.includes(this)) return; if (lib.node.observing.includes(this)) return;
var player = lib.playerOL[this.id]; var player = lib.playerOL[this.id];
if (player) { if (player) {
@ -12238,7 +12286,10 @@ export class Library {
}, player); }, player);
} }
}, },
unauto: function () { /**
* @this {import("./element/client.js").Client}
*/
unauto() {
if (lib.node.observing.includes(this)) return; if (lib.node.observing.includes(this)) return;
var player = lib.playerOL[this.id]; var player = lib.playerOL[this.id];
if (player) { if (player) {
@ -12249,18 +12300,21 @@ export class Library {
}, player); }, player);
} }
}, },
exec: function (func) { exec(func) {
// if(typeof func=='function'){ // if(typeof func=='function'){
// var args=Array.from(arguments); // var args=Array.from(arguments);
// args.shift(); // args.shift();
// func.apply(this,args); // func.apply(this,args);
// } // }
}, },
log: function () { /**
* @this {import("./element/client.js").Client}
*/
log() {
var items = []; var items = [];
try { try {
for (var i = 0; i < arguments.length; i++) { for (var i = 0; i < arguments.length; i++) {
items.push(security.eval(`return ${arguments[i]}`)); items.push(this.sandbox.exec(`return ${arguments[i]}`));
} }
} catch (e) { } catch (e) {
this.send("log", ["err"]); this.send("log", ["err"]);

View File

@ -142,23 +142,21 @@ export class LibInit {
if (!Array.isArray(message) || typeof lib.message.server[message[0]] !== "function") { if (!Array.isArray(message) || typeof lib.message.server[message[0]] !== "function") {
throw "err"; throw "err";
} }
if (!client.sandbox) client.sandbox = security.createSandbox();
// @ts-ignore // @ts-ignore
security.enterSandbox(client.sandbox); if (client.sandbox) security.enterSandbox(client.sandbox);
try { try {
for (var i = 1; i < message.length; i++) { for (var i = 1; i < message.length; i++) {
message[i] = get.parsedResult(message[i]); message[i] = get.parsedResult(message[i]);
} }
} finally { } finally {
// @ts-ignore // @ts-ignore
security.exitSandbox(client.sandbox); if (client.sandbox) security.exitSandbox();
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);
console.log("invalid message: " + messagestr); console.log("invalid message: " + messagestr);
return; return;
} }
lib.message.server[message.shift()].apply(client, message);
}); });
ws.on("close", function () { ws.on("close", function () {
client.close(); client.close();
@ -609,10 +607,10 @@ export class LibInit {
* @param {Function} func * @param {Function} func
*/ */
function Legacy(func) { function Legacy(func) {
const { Marshal } = security.importSandbox();
//Remove all comments //Remove all comments
//移除所有注释 //移除所有注释
let str = func let str = Marshal.decompileFunction(func)
.toString()
.replace(/((?:(?:^[ \t]*)?(?:\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/)))?|\/\/(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/))|(?=\r?\n))))+)|("(?:\\[\s\S]|[^"\\])*"|'(?:\\[\s\S]|[^'\\])*'|(?:\r?\n|[\s\S])[^/"'\\\s]*)/gm, "$2") .replace(/((?:(?:^[ \t]*)?(?:\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/)))?|\/\/(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/))|(?=\r?\n))))+)|("(?:\\[\s\S]|[^"\\])*"|'(?:\\[\s\S]|[^'\\])*'|(?:\r?\n|[\s\S])[^/"'\\\s]*)/gm, "$2")
.trim(); .trim();
//获取第一个 { 后的所有字符 //获取第一个 { 后的所有字符

View File

@ -20,7 +20,6 @@ const SandboxSignal_GetMarshalledProxy = Symbol("GetMarshalledProxy");
const SandboxSignal_SetMarshalledProxy = Symbol("SetMarshalledProxy"); const SandboxSignal_SetMarshalledProxy = Symbol("SetMarshalledProxy");
const SandboxSignal_GetWindow = Symbol("GetWindow"); const SandboxSignal_GetWindow = Symbol("GetWindow");
const SandboxSignal_GetPromise = Symbol("GetPromise"); const SandboxSignal_GetPromise = Symbol("GetPromise");
const SandboxSignal_IsArray = Symbol("IsArray");
const SandboxSignal_EnterDomain = Symbol("EnterDomain"); const SandboxSignal_EnterDomain = Symbol("EnterDomain");
const SandboxSignal_ExitDomain = Symbol("ExitDomain"); const SandboxSignal_ExitDomain = Symbol("ExitDomain");
const SandboxSignal_ListDomain = Symbol("ListDomain"); const SandboxSignal_ListDomain = Symbol("ListDomain");
@ -31,6 +30,7 @@ const SandboxSignal_TrapDomain = Symbol("TrapDomain");
const SandboxSignal_DiapatchMonitor = Symbol("DiapatchMonitor"); const SandboxSignal_DiapatchMonitor = Symbol("DiapatchMonitor");
const SandboxSignal_ListMonitor = Symbol("ListMonitor"); const SandboxSignal_ListMonitor = Symbol("ListMonitor");
const SandboxSignal_ExposeInfo = Symbol("ExposeInfo"); const SandboxSignal_ExposeInfo = Symbol("ExposeInfo");
const SandboxSignal_TryFunctionRefs = Symbol("TryFunctionRefs");
/** /**
* ```plain * ```plain
@ -52,17 +52,17 @@ function isPrimitive(obj) {
* ``` * ```
*/ */
class AccessAction { class AccessAction {
// static CALL = 0; // apply // static CALL = 0; // apply
// static NEW = 1; // construct // static NEW = 1; // construct
// static READ = 2; // get // static READ = 2; // get
// static WRITE = 3; // set // static WRITE = 3; // set
// static DESCRIBE = 4; // getOwnPropertyDescriptor // static DESCRIBE = 4; // getOwnPropertyDescriptor
// static DEFINE = 5; // defineProperty // static DEFINE = 5; // defineProperty
// static TRACE = 6; // getPrototypeOf // static TRACE = 6; // getPrototypeOf
// static META = 7; // setPrototypeOf // static META = 7; // setPrototypeOf
// static SEAL = 8; // preventExtensions // static SEAL = 8; // preventExtensions
// static EXISTS = 9; // has // static EXISTS = 9; // has
// static LIST = 10; // ownKeys // static LIST = 10; // ownKeys
// static DELETE = 11; // delete // static DELETE = 11; // delete
/** ```Reflect.apply``` */ /** ```Reflect.apply``` */
@ -443,6 +443,36 @@ const MARSHALLED_LIST = Object.freeze([
"/cancelIdleCallback", "/cancelIdleCallback",
"/queueMicrotask", "/queueMicrotask",
"/MutationObserver", "/MutationObserver",
// 根据狂神喵提供的问题
// 我们对console进行迁移
"/console/debug",
"/console/error",
"/console/info",
"/console/log",
"/console/warn",
"/console/dir",
"/console/dirxml",
"/console/table",
"/console/trace",
"/console/group",
"/console/groupCollapsed",
"/console/groupEnd",
"/console/clear",
"/console/count",
"/console/countReset",
"/console/assert",
"/console/profile",
"/console/profileEnd",
"/console/time",
"/console/timeLog",
"/console/timeEnd",
"/console/timeStamp",
"/console/context",
"/console/createTask",
"/console/memory",
// 另外补充这两个可能的函数哦
"/alert",
"/confirm",
]); ]);
/** /**
@ -532,6 +562,8 @@ class Globals {
if (!Globals.#globals.has(domain)) { if (!Globals.#globals.has(domain)) {
const window = domain[SandboxExposer](SandboxSignal_GetWindow); const window = domain[SandboxExposer](SandboxSignal_GetWindow);
const globals = [new WeakMap(), {}]; const globals = [new WeakMap(), {}];
// @ts-ignore
Globals.#globals.set(domain, globals);
// 检查是否是顶级域 // 检查是否是顶级域
if (Globals.#topGlobals) { if (Globals.#topGlobals) {
@ -572,9 +604,6 @@ class Globals {
globals[0].set(obj, key); globals[0].set(obj, key);
globals[1][key] = obj; globals[1][key] = obj;
} }
// @ts-ignore
Globals.#globals.set(domain, globals);
} }
} }
@ -1057,6 +1086,8 @@ class DomainMonitors {
/** @type {WeakMap<Domain, DomainMonitors>} */ /** @type {WeakMap<Domain, DomainMonitors>} */
static #domainMonitors = new WeakMap(); static #domainMonitors = new WeakMap();
/** @type {WeakMap<Object, Set<Monitor>>} */
#targetMonitorsMap = new WeakMap();
/** @type {WeakMap<Domain, Object<number, Set<Monitor>>>} */ /** @type {WeakMap<Domain, Object<number, Set<Monitor>>>} */
#monitorsMap = new WeakMap(); #monitorsMap = new WeakMap();
@ -1075,9 +1106,25 @@ class DomainMonitors {
actions, actions,
allowDomains, allowDomains,
disallowDomains, disallowDomains,
targets,
] = Monitor[SandboxExposer2] ] = Monitor[SandboxExposer2]
(SandboxSignal_ExposeInfo, monitor); (SandboxSignal_ExposeInfo, monitor);
// 如果指定了目标,使用目标 Monitor 集合
// 以此对于特定对象的 Monitor 进行性能优化
if (targets) {
for (const target of targets) {
let monitors = thiz.#targetMonitorsMap.get(target);
if (!monitors)
thiz.#targetMonitorsMap.set(target, monitors = new Set());
monitors.add(monitor);
}
return;
}
function addToActionMap(actionMap) { function addToActionMap(actionMap) {
for (const action of actions) { for (const action of actions) {
let monitorMap = actionMap[action]; let monitorMap = actionMap[action];
@ -1133,9 +1180,24 @@ class DomainMonitors {
actions, actions,
allowDomains, allowDomains,
disallowDomains, disallowDomains,
targets,
] = Monitor[SandboxExposer2] ] = Monitor[SandboxExposer2]
(SandboxSignal_ExposeInfo, monitor); (SandboxSignal_ExposeInfo, monitor);
// 对于指定了目标的 Monitor 特殊处理
if (targets) {
for (const target of targets) {
const monitors = thiz.#targetMonitorsMap.get(target);
if (!monitors)
continue;
monitors.delete(monitor);
}
return;
}
function removeFromActionMap(actionMap) { function removeFromActionMap(actionMap) {
for (const action of actions) { for (const action of actions) {
const monitorMap = actionMap[action]; const monitorMap = actionMap[action];
@ -1183,20 +1245,32 @@ class DomainMonitors {
* @param {Domain} sourceDomain * @param {Domain} sourceDomain
* @param {Domain} targetDomain * @param {Domain} targetDomain
* @param {number} action * @param {number} action
* @returns {Set<Monitor>?} * @param {Object} target
* @returns {Array<Monitor>?}
*/ */
static #getMonitorsBy = function (sourceDomain, targetDomain, action) { static #getMonitorsBy = function (sourceDomain, targetDomain, action, target) {
const instance = DomainMonitors.#domainMonitors.get(sourceDomain); const instance = DomainMonitors.#domainMonitors.get(sourceDomain);
if (!instance) if (!instance)
return null; return null;
const targetMap = instance.#targetMonitorsMap.get(target);
const actionMap = instance.#monitorsMap.get(targetDomain); const actionMap = instance.#monitorsMap.get(targetDomain);
const actionMonitors = actionMap && actionMap[action];
if (!actionMap || !(action in actionMap)) let array = null;
return null;
return actionMap[action]; if (targetMap)
array = [...targetMap]; // 优先执行指定目标的 Monitor
if (actionMonitors) {
if (!array)
array = [...actionMonitors];
else
array.push(...actionMonitors);
}
return array;
} }
/** /**
@ -1222,9 +1296,14 @@ class DomainMonitors {
actions, actions,
allowDomains, allowDomains,
disallowDomains, disallowDomains,
targets,
] = Monitor[SandboxExposer2] ] = Monitor[SandboxExposer2]
(SandboxSignal_ExposeInfo, monitor); (SandboxSignal_ExposeInfo, monitor);
// 指定了目标的 Monitor 不参与新运行域处理
if (targets)
continue;
// 判断新增的 Domain 是否是 Monitor 监听的目标 // 判断新增的 Domain 是否是 Monitor 监听的目标
if (allowDomains if (allowDomains
&& !allowDomains.has(domain)) && !allowDomains.has(domain))
@ -1355,14 +1434,16 @@ class DomainMonitors {
Object.freeze(nameds); Object.freeze(nameds);
// 获取可能的 Monitor 集合 // 获取可能的 Monitor 集合
const monitorMap = DomainMonitors.#getMonitorsBy(sourceDomain, targetDomain, action); const monitorMap = DomainMonitors.#getMonitorsBy(
sourceDomain, targetDomain, action, args[0]);
const result = { const result = {
preventDefault: false, preventDefault: false,
stopPropagation: false, stopPropagation: false,
returnValue: undefined, returnValue: undefined,
}; };
if (!monitorMap || monitorMap.size == 0) if (!monitorMap || !monitorMap.length)
return result; return result;
const access = { const access = {
@ -1386,6 +1467,9 @@ class DomainMonitors {
setReturnValue(value) { setReturnValue(value) {
result.returnValue = value; result.returnValue = value;
}, },
throwDenied(message = null) {
throw new RangeError(message || "封送对象的源运行域禁止了此项操作");
},
}); });
// 遍历并尝试分发监听事件 // 遍历并尝试分发监听事件
@ -1450,7 +1534,7 @@ class DomainMonitors {
* monitor.require("property", "value"); // 指定监听 value 属性 * monitor.require("property", "value"); // 指定监听 value 属性
* monitor.filter((access, nameds) => nameds.value >= 0); // 过滤掉大于等于 0 的修改 * monitor.filter((access, nameds) => nameds.value >= 0); // 过滤掉大于等于 0 的修改
* monitor.then((access, nameds, control) => { * monitor.then((access, nameds, control) => {
* control.overrideParameter("value", 0); // 将要修改的新值改回 0 * control.overrideParameter("value", 0); // 将要修改的新值改回 0
* }); * });
* monitor.start(); // 启动Monitor * monitor.start(); // 启动Monitor
* ``` * ```
@ -1595,37 +1679,41 @@ class Monitor {
/** /**
* *
* @typedef {"target" | "thisArg" | "arguments" | "newTarget" | "property" | "descriptor" | "receiver" | "prototype" | "value"} PropertyKey * @typedef {"target" | "thisArg" | "arguments"
* | "newTarget" | "property" | "descriptor"
* | "receiver" | "prototype" | "value"
* } PropertyKey
* *
* @typedef {{ * @typedef {{
* domain: Domain, * domain: Domain,
* action: number, * action: number,
* }} Access * }} Access
* *
* @typedef {{ * @typedef {{
* target: Object, * target: Object,
* thisArg?: Object, * thisArg?: Object,
* arguments?: Array<any>, * arguments?: Array<any>,
* newTarget?: Function, * newTarget?: Function,
* property?: string | symbol, * property?: string | symbol,
* descriptor?: { * descriptor?: {
* value?: any, * value?: any,
* writable?: boolean, * writable?: boolean,
* get?: () => any, * get?: () => any,
* set?: (value: any) => void, * set?: (value: any) => void,
* enumerable?: boolean, * enumerable?: boolean,
* configurable?: boolean, * configurable?: boolean,
* }, * },
* receiver?: Object, * receiver?: Object,
* prototype?: Object, * prototype?: Object,
* value?: any, * value?: any,
* }} Nameds * }} Nameds
* *
* @typedef {{ * @typedef {{
* preventDefault: () => void, * preventDefault: () => void,
* stopPropagation: () => void, * stopPropagation: () => void,
* overrideParameter: (name: PropertyKey, value: any) => void, * overrideParameter: (name: PropertyKey, value: any) => void,
* setReturnValue: (value: any) => void, * setReturnValue: (value: any) => void,
* throwDenied: (message?: string) => never,
* }} Control * }} Control
* *
*/ */
@ -1774,7 +1862,8 @@ class Monitor {
return [ return [
thiz.#actions, thiz.#actions,
thiz.#allowDomains, thiz.#allowDomains,
thiz.#disallowDomains thiz.#disallowDomains,
thiz.#checkInfo["target"],
]; ];
} }
@ -1895,8 +1984,8 @@ class Marshal {
* ``` * ```
* *
* @typedef {[ * @typedef {[
* Domain, * Domain,
* Object, * Object,
* ]} Reverted * ]} Reverted
* *
* @param {any} proxy * @param {any} proxy
@ -2000,6 +2089,29 @@ class Marshal {
return domain; return domain;
} }
/**
* ```plain
* 对于封送或未封送的函数执行转字符串操作
* ```
*
* @param {Function} func
*/
static decompileFunction(func) {
if (typeof func !== "function")
throw new TypeError("无效的函数对象");
if (Marshal.#marshalledProxies.has(func))
[, func] = Marshal.#revertProxy(func);
const refs = Sandbox[SandboxExposer2]
(SandboxSignal_TryFunctionRefs, func);
if (refs)
return refs;
return Function.prototype.toString.call(func);
}
/** /**
* ```plain * ```plain
* 陷入某个运行域并执行代码 * 陷入某个运行域并执行代码
@ -2073,6 +2185,41 @@ class Marshal {
} }
/** /**
* ```plain
* 根据目标对象的特征复制一个基本对象
* ```
*
* @param {Object} src
* @returns {any}
*/
static #clonePureObject = function (src) {
let cloned;
if (typeof src === "function") {
const descriptor = Reflect.getOwnPropertyDescriptor(src, "prototype");
if (descriptor
&& descriptor.value
&& !descriptor.enumerable
&& !descriptor.configurable
&& descriptor.value.constructor === src)
cloned = function () { };
else
cloned = () => { };
} else if (Array.isArray(src))
cloned = [];
else
cloned = {};
Reflect.setPrototypeOf(cloned, null);
cloned["target"] = src;
return cloned;
}
/**
* ```plain
* 封送核心函数
* ```
*
* @param {Object} obj * @param {Object} obj
* @param {Domain} targetDomain * @param {Domain} targetDomain
* @returns {Object} * @returns {Object}
@ -2135,9 +2282,15 @@ class Marshal {
if (cached) if (cached)
return cached; return cached;
// 创建一个空白对象防止JavaScript的一些奇怪错误
const pure = Marshal.#clonePureObject(target);
// 设置属性方便调试
pure.sourceDomain = sourceDomain;
pure.targetDomain = targetDomain;
// 创建封送代理 // 创建封送代理
const proxy = new Proxy(target, { const proxy = new Proxy(pure, {
apply(target, thisArg, argArray) { apply(_, thisArg, argArray) {
const defaultApply = () => { const defaultApply = () => {
const marshalledThis = Marshal.#marshal(thisArg, sourceDomain); const marshalledThis = Marshal.#marshal(thisArg, sourceDomain);
const marshalledArgs = Marshal.#marshalArray(argArray, sourceDomain); const marshalledArgs = Marshal.#marshalArray(argArray, sourceDomain);
@ -2169,7 +2322,7 @@ class Marshal {
return defaultApply(); return defaultApply();
}, },
construct(target, argArray, newTarget) { construct(_, argArray, newTarget) {
const marshalledArgs = Marshal.#marshalArray(argArray, sourceDomain); const marshalledArgs = Marshal.#marshalArray(argArray, sourceDomain);
const marshalledNewTarget = Marshal.#marshal(newTarget, sourceDomain); const marshalledNewTarget = Marshal.#marshal(newTarget, sourceDomain);
@ -2192,7 +2345,7 @@ class Marshal {
return Marshal.#marshal(result, targetDomain); return Marshal.#marshal(result, targetDomain);
}); });
}, },
defineProperty(target, property, attributes) { defineProperty(_, property, attributes) {
const isSourceDomain = sourceDomain === Domain.current; const isSourceDomain = sourceDomain === Domain.current;
if (!isSourceDomain) { if (!isSourceDomain) {
@ -2247,7 +2400,7 @@ class Marshal {
? domainTrapAction() ? domainTrapAction()
: Marshal.#trapDomain(sourceDomain, domainTrapAction); : Marshal.#trapDomain(sourceDomain, domainTrapAction);
}, },
deleteProperty(target, p) { deleteProperty(_, p) {
return Marshal.#trapDomain(sourceDomain, () => { return Marshal.#trapDomain(sourceDomain, () => {
const rule = ruleRef.rule; const rule = ruleRef.rule;
@ -2265,7 +2418,7 @@ class Marshal {
return Reflect.deleteProperty(...args); return Reflect.deleteProperty(...args);
}); });
}, },
get(target, p, receiver) { get(_, p, receiver) {
// 因为 get 的东西最多,所以对此追加注释 // 因为 get 的东西最多,所以对此追加注释
// 其他的拦截器都是与 get 类似 // 其他的拦截器都是与 get 类似
@ -2304,7 +2457,7 @@ class Marshal {
return Marshal.#marshal(result, targetDomain); return Marshal.#marshal(result, targetDomain);
}); });
}, },
getOwnPropertyDescriptor(target, p) { getOwnPropertyDescriptor(_, p) {
const isSourceDomain = Domain.current === sourceDomain; const isSourceDomain = Domain.current === sourceDomain;
const domainTrapAction = () => { const domainTrapAction = () => {
@ -2332,7 +2485,7 @@ class Marshal {
return Marshal.#marshalObject(descriptor, targetDomain); return Marshal.#marshalObject(descriptor, targetDomain);
}); });
}, },
getPrototypeOf(target) { getPrototypeOf(_) {
return Marshal.#trapDomain(sourceDomain, () => { return Marshal.#trapDomain(sourceDomain, () => {
const rule = ruleRef.rule; const rule = ruleRef.rule;
@ -2356,7 +2509,7 @@ class Marshal {
return marshalledResult; return marshalledResult;
}); });
}, },
has(target, p) { has(_, p) {
const isSourceDomain = Domain.current === sourceDomain; const isSourceDomain = Domain.current === sourceDomain;
const domainTrapAction = () => { const domainTrapAction = () => {
const rule = ruleRef.rule; const rule = ruleRef.rule;
@ -2380,10 +2533,10 @@ class Marshal {
return Marshal.#trapDomain(sourceDomain, domainTrapAction); return Marshal.#trapDomain(sourceDomain, domainTrapAction);
}, },
isExtensible(target) { isExtensible(_) {
return Reflect.isExtensible(target); return Reflect.isExtensible(target);
}, },
ownKeys(target) { ownKeys(_) {
return Marshal.#trapDomain(sourceDomain, () => { return Marshal.#trapDomain(sourceDomain, () => {
const rule = ruleRef.rule; const rule = ruleRef.rule;
@ -2410,7 +2563,7 @@ class Marshal {
}); });
}, },
preventExtensions(target) { preventExtensions(_) {
return Marshal.#trapDomain(sourceDomain, () => { return Marshal.#trapDomain(sourceDomain, () => {
const rule = ruleRef.rule; const rule = ruleRef.rule;
@ -2428,7 +2581,7 @@ class Marshal {
return Reflect.preventExtensions(...args); return Reflect.preventExtensions(...args);
}); });
}, },
set(target, p, newValue, receiver) { set(_, p, newValue, receiver) {
const marshalledNewValue = Marshal.#marshal(newValue, sourceDomain); const marshalledNewValue = Marshal.#marshal(newValue, sourceDomain);
const marshalledReceiver = Marshal.#marshal(receiver, sourceDomain); const marshalledReceiver = Marshal.#marshal(receiver, sourceDomain);
@ -2450,7 +2603,7 @@ class Marshal {
return Reflect.set(...args); return Reflect.set(...args);
}); });
}, },
setPrototypeOf(target, v) { setPrototypeOf(_, v) {
const marshalledV = Marshal.#marshal(v, sourceDomain); const marshalledV = Marshal.#marshal(v, sourceDomain);
if (Marshal.#marshalledProxies.has(marshalledV)) if (Marshal.#marshalledProxies.has(marshalledV))
@ -2610,30 +2763,30 @@ class Domain {
// 实装这个要代理Object喵 // 实装这个要代理Object喵
// static #hasInstanceMarshalled = function (obj) { // static #hasInstanceMarshalled = function (obj) {
// if (Marshal.isMarshalled(obj)) // if (Marshal.isMarshalled(obj))
// [, obj] = Marshal[SandboxExposer2] // [, obj] = Marshal[SandboxExposer2]
// (SandboxSignal_UnpackProxy, obj); // (SandboxSignal_UnpackProxy, obj);
// return Domain.#hasInstance.call(this, obj); // return Domain.#hasInstance.call(this, obj);
// } // }
// 效率影响不确定,暂不实装 // 效率影响不确定,暂不实装
// static #marshalledThen = function (onfulfilled, onrejected) { // static #marshalledThen = function (onfulfilled, onrejected) {
// if (Marshal.isMarshalled(this)) { // if (Marshal.isMarshalled(this)) {
// const [domain, promise] = Marshal[SandboxExposer2] // const [domain, promise] = Marshal[SandboxExposer2]
// (SandboxSignal_UnpackProxy, this); // (SandboxSignal_UnpackProxy, this);
// const marshaller = value => { // const marshaller = value => {
// return this(trapMarshal(domain, Domain.current, value)); // return this(trapMarshal(domain, Domain.current, value));
// }; // };
// return trapMarshal(domain, Domain.current, promise.then( // return trapMarshal(domain, Domain.current, promise.then(
// marshaller.bind(onfulfilled), // marshaller.bind(onfulfilled),
// marshaller.bind(onrejected) // marshaller.bind(onrejected)
// )); // ));
// } // }
// return [[DefaultThen]].call(this, onfulfilled, onrejected); // return [[DefaultThen]].call(this, onfulfilled, onrejected);
// } // }
/** /**
@ -2881,9 +3034,6 @@ class Domain {
return Domain.#exitDomain(); return Domain.#exitDomain();
case SandboxSignal_ListDomain: case SandboxSignal_ListDomain:
return Domain.#listDomain(); return Domain.#listDomain();
case SandboxSignal_IsArray:
// @ts-ignore
return Domain.#isArray(...args);
} }
} }
} }
@ -2925,6 +3075,8 @@ class Sandbox {
static #domainMap = new WeakMap(); static #domainMap = new WeakMap();
/** @type {Array} */ /** @type {Array} */
static #executingScope = []; static #executingScope = [];
/** @type {WeakMap<Function, string>} */
static #functionRefCodes = new WeakMap();
/** @type {Object} */ /** @type {Object} */
#scope; #scope;
@ -3015,10 +3167,10 @@ class Sandbox {
const code = argArray.slice(-1)[0]; const code = argArray.slice(-1)[0];
const params = argArray.slice(0, -1); const params = argArray.slice(0, -1);
new target(code); // 防止注入
const compiled = Sandbox.#compileCore(thiz, code, null, params, true); const compiled = Sandbox.#compileCore(thiz, code, null, params, true);
compiled[Symbol.toStringTag] = `function (${params.join(", ")}) {\n${code}\n}`; Sandbox.#functionRefCodes.set(compiled,
`function (${params.join(", ")}) {\n${code}\n}`);
return compiled; return compiled;
} }
@ -3486,6 +3638,18 @@ class Sandbox {
return builtName; return builtName;
} }
/**
* @param {Symbol} signal
* @param {...any} args
* @returns
*/
static [SandboxExposer2](signal, ...args) {
switch (signal) {
case SandboxSignal_TryFunctionRefs:
return Sandbox.#functionRefCodes.get(args[0]);
}
}
} }
function sealClass(clazz) { function sealClass(clazz) {

View File

@ -98,6 +98,8 @@ let ModAsyncGeneratorFunction;
function enterSandbox(box) { function enterSandbox(box) {
if (!SANDBOX_ENABLED) if (!SANDBOX_ENABLED)
return; return;
if (!(box instanceof Sandbox))
throw new TypeError("无效的沙盒对象");
if (!Domain.isBelievable(Domain.topDomain)) if (!Domain.isBelievable(Domain.topDomain))
throw "无法在沙盒里面访问"; throw "无法在沙盒里面访问";
@ -117,7 +119,7 @@ function exitSandbox() {
if (!Domain.isBelievable(Domain.topDomain)) if (!Domain.isBelievable(Domain.topDomain))
throw "无法在沙盒里面访问"; throw "无法在沙盒里面访问";
if (!sandboxStack.length) if (!sandboxStack.length)
return; throw new ReferenceError("无法弹出更多的沙盒");
sandboxStack.pop(); sandboxStack.pop();
} }
@ -422,6 +424,7 @@ async function initSecurity({
...ioFuncs.map(n => game[n]).filter(Boolean), ...ioFuncs.map(n => game[n]).filter(Boolean),
...Object.values(game.promises), ...Object.values(game.promises),
defaultEval, defaultEval,
localStorage.setItem,
window.require, window.require,
// @ts-ignore // @ts-ignore
window.define, window.define,
@ -472,6 +475,8 @@ async function initSecurity({
writeRule.setGranted(AccessAction.DEFINE, false); // 禁止重定义属性 writeRule.setGranted(AccessAction.DEFINE, false); // 禁止重定义属性
// 禁止修改 game.promises 的函数 // 禁止修改 game.promises 的函数
Marshal.setRule(game.promises, writeRule); Marshal.setRule(game.promises, writeRule);
// 禁止修改 localStorage
Marshal.setRule(localStorage, writeRule);
// 对于 game 当中访问特定函数我们通过 Monitor 进行拦截 // 对于 game 当中访问特定函数我们通过 Monitor 进行拦截
new Monitor() new Monitor()
@ -481,29 +486,28 @@ async function initSecurity({
// 如果目标是 game 的 ioFuncs 包含的所有函数 // 如果目标是 game 的 ioFuncs 包含的所有函数
.require("target", game) .require("target", game)
.require("property", ...ioFuncs) .require("property", ...ioFuncs)
.require("property", "ws") .require("property", "ws", "sandbox")
// 抛出异常 // 抛出异常
.then((access, nameds, control) => { .then((access, nameds, control) => {
throw `禁止沙盒修改 \`game.${nameds.prototype}\` 属性`; throw `有不信任的代码修改 \`game.${String(nameds.property)}\` 属性`;
}) })
// 让 Monitor 开始工作 // 让 Monitor 开始工作
.start(); // 差点忘记启动了喵 .start(); // 差点忘记启动了喵
// 现在 parsex 已经禁止传递字符串,这段 Monitor 不需要了
// 监听原型、toStringTag的更改 // 监听原型、toStringTag的更改
// const toStringTag = Symbol.toStringTag; const toStringTag = Symbol.toStringTag;
// new Monitor() new Monitor()
// .action(AccessAction.WRITE) .action(AccessAction.WRITE)
// .action(AccessAction.DEFINE) .action(AccessAction.DEFINE)
// .action(AccessAction.META) .action(AccessAction.META)
// .require("property", toStringTag) .require("property", toStringTag)
// .then((access, nameds, control) => { .then((access, nameds, control) => {
// // 阻止原型、toStringTag的更改 // 阻止原型、toStringTag的更改
// control.preventDefault(); control.preventDefault();
// control.stopPropagation(); control.stopPropagation();
// control.setReturnValue(false); control.setReturnValue(false);
// }) })
// .start(); .start();
if (SANDBOX_AUTOTEST) { if (SANDBOX_AUTOTEST) {
// 一个测试循环喵 // 一个测试循环喵
@ -646,12 +650,12 @@ function getIsolatedsFrom(item) {
* ``` * ```
* *
* @returns {{ * @returns {{
* AccessAction: typeof import("./sandbox.js").AccessAction, * AccessAction: typeof import("./sandbox.js").AccessAction,
* Domain: typeof import("./sandbox.js").Domain, * Domain: typeof import("./sandbox.js").Domain,
* Marshal: typeof import("./sandbox.js").Marshal, * Marshal: typeof import("./sandbox.js").Marshal,
* Monitor: typeof import("./sandbox.js").Monitor, * Monitor: typeof import("./sandbox.js").Monitor,
* Rule: typeof import("./sandbox.js").Rule, * Rule: typeof import("./sandbox.js").Rule,
* Sandbox: typeof import("./sandbox.js").Sandbox, * Sandbox: typeof import("./sandbox.js").Sandbox,
* }} * }}
*/ */
function importSandbox() { function importSandbox() {
@ -885,11 +889,11 @@ function setupPolyfills(sandbox) {
} }
// 测试暴露喵 // 测试暴露喵
// Reflect.defineProperty(window, "sandbox", { Reflect.defineProperty(window, "sandbox", {
// get: () => defaultSandbox, get: () => defaultSandbox,
// set: () => { }, set: () => { },
// configurable: true, configurable: true,
// }); });
const exports = { const exports = {
enterSandbox, enterSandbox,