不仅是读写:解密 Linux 文件权限与特权提升
不只是读写:解密 Linux 文件权限与特权提升
在 Linux 中,“一切皆文件”。文件权限不仅定义了数据的可访问性,更是系统安全与特权管理的核心。
权限模型回顾
除了标准的 UGO(User/Group/Others)读、写、执行权限外,Linux 还引入了特殊权限位,它们往往是系统安全防护的边界,也是攻击者眼中的“金矿”。
关键特殊位:SUID/SGID/Sticky Bit
- SUID (Set User ID):赋予执行文件,进程运行时将以该文件的所有者(通常是 root)身份运行。这是系统程序(如
passwd)实现非特权用户修改密码的关键。 - SGID (Set Group ID):赋予文件时效果类似 SUID;赋予目录时,在该目录下创建的新文件会自动继承父目录的所属组。
- Sticky Bit (粘滞位):通常用于共享目录(如
/tmp),确保用户只能删除属于自己的文件。
提权:为何 SUID 是危险的?
如果一个配置不当的程序拥有 SUID 位(且所有者为 root),它就可能成为提权的跳板。
场景分析:利用 SUID 执行任意命令
攻击者可以寻找具备 SUID 权限的、允许执行系统命令的程序。
例如,若程序调用了未定义完整路径的 system() 函数,攻击者可通过修改 PATH 环境变量,让该程序执行恶意的同名可执行文件,从而获得 root 权限。
# 检查系统中所有具备 SUID 权限的文件
find / -perm -4000 -type f 2>/dev/null
生产环境的防御建议
对于构建 Agent 系统的工程师,如何通过权限设计防范此类风险?
- 最小权限原则 (PoLP):
- 限制 SUID:定期扫描系统中不必要的 SUID 文件。
- 容器化隔离:将 Agent 运行在受限的 Docker 容器中,利用 Namespace 和 cgroups 进行内核级隔离,即便 Agent 被攻破,其权限也仅限于容器内部。
- 可观测性增强:
- 在敏感目录(如
/etc,/usr/bin)部署auditd监控,一旦出现异常的chmod或chown操作立即触发告警。
- 在敏感目录(如
- 防范脚本注入:
- 在代码层面,永远不要直接执行不可信的 shell 字符串,使用结构化的 API 调用。
总结
权限控制是 Linux 内核安全的第一道防线。作为部署工程师,理解这些特权位的生效机制,不仅能帮助我们更优雅地进行系统运维,更能建立起防御漏洞利用的体系化思维。
一份基座,N 套人格:LoRA 热插拔的原理、接口与真实成本
一份基座,N 套人格:LoRA 热插拔的原理、接口与真实成本
如果你曾经为"想要三个不同风格/不同领域的模型"而准备下载三份模型文件,那这篇文章能帮你省下大量磁盘、显存和等待时间。
核心结论先给出:一份基座模型 + 若干个几十 MB 的 LoRA 适配器,可以在运行时自由切换甚至叠加,不重启、不重新导出、不复制大文件。 在生产环境里,这套做法已经有人把每月的 GPU 成本从约 2.4 万美元降到约 1200 美元。
一、LoRA 是什么:给大模型贴一张"专用贴纸"
微调(Fine-tuning)传统上指:拿一份预训练模型,用你自己的数据继续训练,得到一份完整的新模型文件。问题很明显——一个 8B 模型就是 16 GB,你想要三个风格,就要存三份 48 GB,跑起来还要三份显存。
LoRA(Low-Rank Adaptation,低秩适配)的做法完全不同。它发现了一个经验规律:模型从"通用"变成"某个专用领域",权重需要改变的部分其实很少。于是它不动原来的权重,而是额外学两个很小的矩阵。
用数学写出来非常简单:
原始输出: y = x · W
加上 LoRA: y = x · W + scale · x · A · B
其中 W 是原来那个巨大的权重矩阵(冻结、不动),而 A 和 B 是两个"瘦长"的小矩阵。它们的尺寸是刻意设计成很窄的——中间那个很窄的维度就是 rank(秩),通常取 16 到 64。
它为什么能这么小?举个例子:一个 8B 模型在 rank 16 下的可训练参数约 1500 万,而模型本身有 80 亿冻结参数——你只训练了不到 0.2% 的参数,最终产物就是几千万个数字,落到磁盘上大约 40 MB(rank 16)。
让 2B 当侦察兵:投机解码的原理、实测数据与失效边界
让 2B 当侦察兵:投机解码的原理、实测数据与失效边界
上一篇我们提到,2B 小模型最"值钱"的用法之一是给大模型当草稿机。这个技术的正式名字叫投机解码(Speculative Decoding)。它的效果相当戏剧化:有人实测把 27B 模型的生成速度从 25 tok/s 提到 45 tok/s,也有人实测把 60 tok/s 掉到 45 tok/s——用的是同一个技术。
本文面向没有推理优化背景的读者,先讲清"为什么能加速",再讲清"为什么有时反而变慢",中间穿插多组真实实测数据。
一、先搞懂一个反直觉的事实:大模型生成慢,不是因为算不过来
这是理解投机解码的唯一前提。
大模型生成文字是一个字一个字往外蹦的(术语叫自回归)。每蹦一个字,都要把整个模型的全部权重从显存里完整读一遍。以 27B 模型 Q4 量化为例,权重约 16 GB——也就是说,每生成一个字,就要搬运 16 GB 数据。
这带来一个关键结论:瓶颈在内存带宽,不在计算能力。显卡的计算单元绝大部分时间在空转等数据。用一张带宽 936 GB/s 的 RTX 3090 举例,理论上限就是 936 ÷ 16 ≈ 58 tok/s,无论这张卡的算力有多强。
所以加速的方向有两条:
- 减少要搬运的数据量(量化、用小模型)——代价是质量。
- 让空转的算力干点活——这正是投机解码的思路,且不损失任何质量。
二、核心思路:让便宜的小模型去"猜",让贵的大模型去"批改"
打个比方。大模型是个写文章很慢但水平很高的资深编辑,小模型是个打字飞快但水平一般的实习生。
- 传统方式:编辑一个字一个字亲自写,写一个字想一下,慢。
- 投机解码:实习生先一口气猜出接下来 5 个字,编辑拿过来一次性扫一眼——“前 3 个对,第 4 个不对”。于是前 3 个字直接采纳(几乎白赚),第 4 个字由编辑自己改,第 5 个字扔掉重来。
关键在于:编辑一次性检查 5 个字的成本,和检查 1 个字的成本几乎一样。因为权重的搬运量在一次前向传播里是固定的,检查 5 个 token 只是把这条数据多算几遍而已——而算力本来就是空闲的。
curl 不止是下载工具:AI 与 Agent 开发中的十二类实战场景
curl 不止是下载工具:AI 与 Agent 开发中的十二类实战场景
多数人学 curl 的第一课是"下载文件",此后便把它归类为 wget 的同类。但在 AI 与 Agent 工程中,curl 的角色完全不同:它是唯一一个在协议层完全透明、零依赖、可在任何环境逐字复现的 HTTP 客户端。当需要回答"这个模型接口到底返回了什么"“延迟究竟卡在哪一段"“Agent 的工具调用为什么静默失败"时,curl 往往比任何 SDK 都更快给出答案——因为它不隐藏任何东西。
本文以 curl 8.22.0(2026-09-02 发布,第 276 个版本,命令行选项累计 278 个)为基线,梳理十二类高频场景。所有命令均可直接粘贴执行,只需替换端点与凭据。
一、LLM 接口调试的四个基本动作
1.1 最小可复现调用
--json(7.82.0 引入)是单次调用最省事的写法,它等价于同时做三件事:把数据作为 --data-binary 发送、设置 Content-Type: application/json、设置 Accept: application/json。
curl -sS https://api.deepseek.com/chat/completions \
-H "Authorization: Bearer $DEEPSEEK_API_KEY" \
--json '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "只回复 OK"}],
"temperature": 0
}' \
-w '\n[%{http_code} | %{time_total}s | %{size_download}B]\n'
三点值得强调:
理解 Linux 进程:信号机制与守护进程的生与死
理解 Linux 进程:信号机制与守护进程的生与死
在 Linux 系统中,进程是资源分配和调度的主体。理解进程的运行状态、如何终止以及如何实现优雅的重启,是保障服务高可用的基石。
进程的生命周期
进程从 fork() 开始,经历运行、睡眠、僵尸 (Zombie) 等状态。
- 运行状态 (R):正在 CPU 上执行或在运行队列中等待。
- 睡眠状态 (S/D):等待某种事件发生(如 I/O 完成)。
- 僵尸状态 (Z):进程已终止但尚未被父进程回收,占用了进程描述符 (PID)。
信号 (Signals):进程间的“紧急通信”
信号是 Linux 中一种异步通知机制,用于通知进程发生了特定事件。
| 信号 | 名称 | 作用 |
|---|---|---|
| SIGTERM | 终止信号 | 请求进程优雅退出,允许进程清理资源 |
| SIGKILL | 强制杀死 | 立即终止进程,进程无法捕获或忽略 |
| SIGHUP | 终端挂起 | 常用于通知守护进程重新加载配置文件 |
优雅退出的艺术
许多线上事故源于不当的进程终止方式。
1. 为什么拒绝 kill -9?
强制使用 SIGKILL 会打断进程正在进行的事务(如数据库写入),导致数据不一致或内存溢出。
2. 优雅终止流程
守护进程应捕获 SIGTERM 信号:
- 停止接收新请求。
- 处理完当前正在进行的任务。
- 释放已申请的 socket、数据库连接。
- 进程退出。
# 推荐做法:首先发送 SIGTERM
kill <PID>
# 等待几秒后,如果进程依然存在,再视情况决定是否强制清除
# (但尽量避免直接使用 kill -9)
运维场景
在生产环境中,部署新的配置后,通常通过发送 SIGHUP 信号触发服务的“平滑重启”,实现零停机部署:
别把 2B 当小号 GPT-4:llama.cpp + Qwen 2B 的六种实战玩法
别把 2B 当小号 GPT-4:llama.cpp + Qwen 2B 的六种实战玩法
在本地跑一个 2B 级别的模型(Qwen2.5-1.5B、Qwen3-1.7B 这一类),最常见的失望是「它连个像样的回答都给不出来」。于是很容易得出一个结论:小模型没用,还得上 32B 甚至 72B。
这个结论错在前提。2B 的定位从来不是"小号 GPT-4",而是流水线里一个廉价、快速、输出确定的专用零件。
在一台普通笔记本上,Qwen 2B(Q4_K_M 量化)的生成速度可以轻松跑到 30 tokens/s 以上,显存占用不到 2GB。这意味着它一秒能吞下几十条请求。真正的技术含量不在于它有多聪明,而在于你如何把任务切到它擅长的那一格。
一句话概括本文的判断:不要让小模型去"思考",要让它去"填空"。
先确认你的跑法
下文所有玩法都建立在 llama-server 上——它提供 OpenAI 兼容接口,任何现成的 SDK 都能直接接。一个够用的启动命令:
./llama-server \
-m models/Qwen2.5-1.5B-Instruct-Q4_K_M.gguf \
-c 4096 \
-np 8 --cont-batching \
--jinja \
--host 127.0.0.1 --port 8080
几个参数值得单独说明:
| 参数 | 作用 |
|---|---|
-c | 上下文长度,小模型不要盲目开大 |
-np --cont-batching | 并行槽位 + 连续批处理,是批量吞吐的关键 |
--jinja | 启用模型自带对话模板,工具调用能力依赖它 |
-ctk q8_0 -ctv q8_0 | KV Cache 量化,长上下文时能省一半显存 |
--mmproj xxx.gguf | 加载视觉投影层,进入多模态模式 |
玩法一:语法约束输出器 —— 最高杠杆的一招
llama.cpp 原生支持 GBNF 语法与 JSON Schema 约束解码。这是它相对于 vLLM、Ollama 最显著的差异点,也是让 2B 从"勉强能用"跳到"近乎可靠"的关键。
从自动化到智能化:Playwright 全栈进阶指南
从自动化到智能化:Playwright 全栈进阶指南
Playwright 已经超越了传统的自动化测试框架,成为了 Web 浏览器协议栈的最强控制端。无论是常规的端到端测试,还是赋予 AI Agent 真实世界的“双手”,Playwright 都是必不可少的工具。
以下是 Playwright 从零到 AI 代理的进阶路线图。
目录
- 环境搭建与架构初探
- 定位器与交互艺术
- 异步测试与高级等待机制
- AI 代理与浏览器控制 (MCP)
- CI/CD 与多环境适配
1. 环境搭建与架构初探
Playwright 基于 WebKit、Chromium 和 Firefox 的原生 CDP(Chrome DevTools Protocol),相比 Selenium 具有更高的稳定性和并发性能。
- 初始化:
npm init playwright@latest或 Python 下pip install playwright - 关键配置:理解配置文件中的
projects(多浏览器并行)与use(全局断言/超时设置)。
2. 定位器与交互艺术
拒绝 XPath,全面拥抱 Web-First Locators。
- 推荐使用:
getByRole,getByLabel,getByPlaceholder,getByTestId。 - 交互逻辑:
click,fill,press均具备自动等待功能,有效减少了手动sleep的“代码异味”。
3. 异步测试与高级等待机制
- 事件驱动:利用
page.waitForEvent和page.waitForResponse精准监控 Web 交互流。 - 状态断言:使用
expect工具链进行原子化断言(如toBeVisible)。 - 网络拦截:使用
page.route进行 API Mock 或异常注入,模拟复杂的后端行为。
4. AI 代理与浏览器控制 (MCP)
这是 Playwright 进阶的巅峰:将浏览器控制权交给 LLM。
AI Agent 工程师学习路线图:Python · Linux · 算法 · Agent 四线合一(2025–2026)
这篇文章把四条通常被分开讲的学习线——Python 工程、Linux 系统、数据结构与算法、AI/Agent——放进同一张路线图。核心判断是:它们不是四份独立的清单,而是围绕同一个能力闭环收敛。Linux 提供 Agent 的运行与隔离边界,Python 提供工程化底座,算法提供检索/规划/限流/缓存的内核,AI/Agent 是最终交付形态。
为什么要四线交叉,而不是串行
AI Agent 工程师与传统后端工程师的差别,不在模型本身,而在围绕模型的系统工程:如何把一次不稳定的模型调用,包装成可恢复、可观测、可评测、可审计的运行时。
这个目标恰好落在四类能力的交集上:
- 模型调用会失败,要重试与限流 → 需要并发与算法;
- 工具调用要触碰真实文件与网络 → 需要 Linux 内核能力做边界;
- 上下文、检索、评测要长期维护 → 需要 Python 工程化;
- 最终形态是一套服务 → 需要容器、部署与可观测性。
所以正确姿势是交叉推进,而不是"先学完 A 再学 B"。下面这张表是整体节奏,每周建议投入 10–15 小时:工作日 1–1.5 小时(以算法为主),周末 5–6 小时(以项目为主)。
| 阶段 | 周期 | Python | Linux | 算法(贯穿) | AI / Agent |
|---|---|---|---|---|---|
| P0 环境基线 | 第 1–2 周 | uv + ruff + pytest + mypy 模板仓库 | Shell 最小集 + SSH 密钥登录 | 复杂度分析 | 通读 Anthropic《Building Effective Agents》 |
| P1 工程打底 | 第 3–10 周 | 类型系统、Pydantic v2、asyncio、FastAPI 流式服务 | 权限/进程/cgroup v2/systemd/网络 | 数组、哈希、双指针、二分、排序 | 原生 API 手写 ReAct Agent;结构化输出 |
| P2 应用构建 | 第 11–24 周 | 数据栈、profiling、打包发布 | 容器化、反代 TLS、VPS 加固、可观测性 | 树、堆、图、DFS/BFS、拓扑排序、回溯、DP 入门 | RAG(混合检索+重排)、评测、MCP server/client |
| P3 Agent 深化 | 第 25–40 周 | 异步多工具运行时、性能优化 | eBPF/perf 排障、Landlock/seccomp 沙箱 | 并查集、Trie、贪心、高级 DP、字符串算法 | LangGraph 1.0 有状态工作流、多 Agent、A2A |
| P4 生产化 | 第 41–52 周 | vLLM 本地部署、服务切换 | 高可用、备份、监控告警 | 图算法综合、位运算、综合题 | 可观测+评测回归+安全防护的生产级 Agent |
一、Python 工程线:把"能跑"变成"敢维护"
版本基线
生产基线选 Python 3.12/3.13,新项目可以上 3.14(3.15 处于 RC 阶段)。需要掌握的语言特性集中在四处:
Agent 行为透明度:设计审计框架的最佳实践
在 AI Agent 的落地场景中,随着 Agent 承载的业务逻辑日益复杂,如何确保其行为的可审计性成为了企业级应用的关键。一个常见的误区是试图通过让模型“自我解释”或直接在工具逻辑中添加追踪标记来审计。
本篇博客探讨如何通过架构解耦,构建一个轻量、防篡改且不干扰模型执行的 Agent 审计框架。
1. 为什么审计逻辑不能内置于工具?
在早期的设计尝试中,我们曾考虑在每个工具(Tool)函数内部添加日志记录逻辑。但很快发现这种做法存在以下问题:
- 代码耦合度高:工具本身的职责是执行任务,审计职责的增加会破坏单一职责原则。
- 一致性难以维护:随着工具库的扩展,审计逻辑的碎片化会导致不同工具的日志格式不统一。
- 难以防御篡改:如果审计逻辑由被审计对象(工具/模型)直接维护,恶意调用可能会通过覆盖日志来掩盖踪迹。
2. 审计框架的核心设计原则
我们建议将审计框架与执行逻辑物理分离,采用 “Dispatcher 拦截模式”:
- 执行与审计物理隔离:所有的审计信息由底层的 Dispatcher(调度器)负责记录,而不由 LLM 直接参与生成或维护。这从根源上消除了模型幻觉干扰审计日志的可能。
- 上下文无感注入:在请求到达系统的一瞬间,框架为该行为链分配唯一的 TraceID。在调用工具前,Dispatcher 负责将 TraceID 像“补丁”一样注入调用参数。
- 结构化元数据存储:不要将审计标记嵌入 Markdown 或模型输出的正文,而是将其作为元数据(Metadata)存储在结构化数据库中。
3. 如何防范 LLM 幻觉干扰?
对于“模型是否会伪造审计标记”的担忧,我们采用以下验证机制:
- 确定性分配:由框架代码负责 ID 生成,LLM 仅作为无状态的执行引擎,对 TraceID 的生成过程不可见、不可控。
- 不可逆日志哈希链:在审计日志写入时,引入哈希链(Hash Chain)。每一条行为日志的 Hash 都包含上一条日志的 Hash。由于 LLM 无法预测后续 Hash 值,它在事后即便试图篡改记录也无法通过校验。
4. 未来展望
Agent 的透明度不是为了限制它的行为,而是为了让开发者在复杂多变的 AI 应用场景中,始终能保持“可控感”。通过将审计层下沉到架构底层,我们将能把更多的资源投入到更高级的决策优化中。
本文基于对 Agent 行为审计框架的实战总结。
容器并非沙箱:为什么 Docker 组权限即是 Root?
在多用户 Linux 环境中,常听到一种说法:将开发者加入 docker 组只是为了方便运行容器,不必输入 sudo。然而,从安全审计的角度来看,将用户加入 docker 组本质上等同于赋予其宿主机的 Root 权限。
为什么 Docker 组 = Root?
Docker 守护进程(dockerd)默认以 root 用户身份运行。由于 docker.sock 套接字文件通常被设置为 root:docker 用户组权限(660),任何属于该组的用户都可以直接通过该套接字与 daemon 通信。
这不仅仅是“方便”的问题。一旦拥有了 docker.sock 的读写权限,你就可以操纵守护进程执行以下操作来实现“容器逃逸”:
- 挂载宿主机根目录:你可以启动一个挂载了宿主机
/的容器,在容器内执行chroot。此时,宿主机的完整文件系统就在你的掌握之中。docker run -v /:/host -it alpine chroot /host - 启动特权容器:通过
--privileged参数,容器可以直接访问宿主机的硬件设备(如/dev/sda),轻易地绕过 Namespace 隔离。
容器不是天然的安全边界
容器技术的本质是 Linux 内核特性的“协同工作”:Namespace 负责隔离进程视图,Cgroup 负责限制资源消耗。但它们并非严密的安防屏障:
- 共享内核:所有容器与宿主机共享同一个内核。一旦发生提权逃逸,攻击者可以直接与内核交互,完全无视 Namespace 的限制。
- Daemon 权限滥用:Docker 的设计哲学是将 daemon 作为系统的“全权委托者”。能够指示 daemon 工作,也就意味着获得了系统的最高控制权。
总结与防御
为了防止因 Docker 权限配置不当引发的系统级风险,建议采取以下防范措施:
- 最小权限原则:严格审计
docker组,移除不必要的用户。 - Rootless Docker:在支持的发行版上使用无根模式,这样即使 daemon 被劫持,它也仅拥有当前用户的权限,无法直接侵害系统核心。
- 拥抱 daemon-less:如使用 Podman,它默认无需守护进程,从架构上彻底消除了
docker.sock带来的 root 等效问题。
作为开发者,我们不仅要利用好工具的便利性,更要看透其背后的权限契约。在 AI 环境部署和 presales 工具链的设计中,始终将“权限最小化”作为第一原则。