mintrt 0.1.0

Security-featured runtime for lua.
//     <<*>> Revenge of snowflake.
//     -^v-  SASWE
//     @ This source code is licensed under a dual license model:
//       1. MPL-2.0 for non-commercial use only
//       2. Commercial License for commercial use
//       See LICENSE and LICENSE_COMMERCIAL files for details.

use log::{info, warn};
// 由于之前的代码耦合过于严重,所以就把按功能分改成了按目标分
use mlua::Lua;
use crate::config;
use crate::security::SDetailedAccessAllow;
use crate::security::env_modify::LuaApis;

pub fn enable_detailed_restrict_fio(lua: &Lua, filectrl: &SDetailedAccessAllow, cmdctrl: &SDetailedAccessAllow) -> mlua::Result<()> {
    // 通义添加:作者不熟悉 Lua,而且通义之前没有给全
    // 文件访问控制:限制在指定目录内,即使用户先允许了 api,访问也受控
    let mut apis = LuaApis::new();
    
    // os 模块:涉及系统命令和文件操作
    apis.insert("os".to_string(), vec![
        "execute".to_string(),   // 执行系统命令 - 使用 cmdctrl 控制
        "remove".to_string(),    // 删除文件 - 使用 filectrl 控制 - 通义补全
        "rename".to_string(),    // 重命名文件 - 使用 filectrl 控制 - 通义补全
    ]);
    
    // io 模块:所有文件访问 API
    apis.insert("io".to_string(), vec![
        "open".to_string(),      // 打开文件
        "close".to_string(),     // 关闭文件
        "read".to_string(),      // 从文件读取
        "write".to_string(),     // 写入文件
        "lines".to_string(),     // 遍历文件行
        "popen".to_string(),     // 进程管道 - 通义补全
    ]);
    
    //由于不会对 mint 和 wing 文件执行分析,所以无法在 hook 时优化,只能把放行规则传给加入的闭包
    //为了保证逻辑的兼容性,hook 时会 HOOKa()->HOOKb()->fCALL()

    //构思:闭包要检查自己能不能放行调用,还是挂钩时就已经知道了要挂拒绝的还是放行的钩子
    //挂钩时做判定很明显要少遇到生命周期的问题,就这么办
    //我先去 main 里把用户自定义逻辑做好和开发者的只读预设相合并 完成
    //可以参照 batch_hook()
    let global = lua.globals();

    for (module, apis) in apis {
        let module_table = global.get::<mlua::Table>(module.clone())?;
        for api in apis {
            // 尝试获取函数,如果不存在则跳过
            let bak_function: Option<mlua::Function> = module_table.get(api.clone()).ok();
            if bak_function.is_none() {
                // API 不存在,跳过
                continue;
            }
            let bak_function = bak_function.unwrap();
            //查询该 api 的数据 - 通义修复:根据 API 类型选择正确的控制表
            let data = match (module.clone().as_str(), api.clone().as_str()) {
                // os.remove 和 os.rename 是文件操作,使用 filectrl
                ("os", "remove") | ("os", "rename") => filectrl.get(&api),
                // os.execute 是系统命令,使用 cmdctrl
                ("os", "execute") => cmdctrl.get(&api),
                // io.* popen 外,使用 cmdctrl  AI 写的代码或存在逻辑漏洞
                ("io", "popen") => cmdctrl.get(&api),
                ("io", _) => filectrl.get(&api),
                _ => None // 不应该到达这里
            };
            // 如果没有找到权限数据,使用空列表作为默认值
            let data = data.cloned().unwrap_or_default();
            //构造该 api 的名称
            let api_name = format!("{}.{}", module.clone(), api.clone());
            let new_func = lua.create_function(move |lua_q, args: mlua::MultiValue| -> Result<mlua::MultiValue, mlua::Error> {
                let data = data.clone();
                if !data.contains(match &args.clone().into_vec()[0].to_string(){
                    Ok(s) => s,
                    Err(_) => return mlua::Result::Err(mlua::Error::RuntimeError("Invalid inputs.".to_string()))
                }
                ) {//可能是多个参数,那么取第一个,然后转换为字符串,如果失败交给 lua 处理防止不便调试
                    info!("Denied api: {}", api_name);
                    return mlua::Result::Err(mlua::Error::RuntimeError("Rejected by mint.".to_string()));
                }
                warn!("Allowed api: {}", api_name);
                // 通义添加:显式指定闭包返回类型为 Result<mlua::MultiValue, mlua::Error>
                // 原因:mlua 的 create_function 需要明确返回类型,参考 env_modify.rs 第 86 行的写法
                // 这样编译器就能正确推断 call 方法的返回类型,满足 IntoLuaMulti trait 约束
                bak_function.call::<mlua::MultiValue>(args)
            })?;
            module_table.set(api, new_func)?;//替换掉原来的函数
        }
        global.set(module, module_table)?;//替换掉原来的模块表
    }

    Ok(())
}