Common Lisp 格式指令的安全解析

Safe Parsing of Format Directives in Common Lisp

我想从输入文件(用户可能已修改也可能未修改)中读取字符串。我想将此字符串视为要使用固定数量的参数调用的格式指令。但是,我知道某些格式指令(特别是想到 ~/)可能会被用于注入函数调用,从而使这种方法本质上是不安全的。

在Common Lisp中使用read解析数据时,该语言提供了*read-eval*动态变量,可以设置为nil来禁用#.代码注入。我正在寻找类似的东西,可以防止格式指令内的代码注入和任意函数调用。

您需要担心的不仅仅是 ~/。漂亮的打印机功能有很多代码扩展的可能性,甚至 ~A 也会导致问题,因为对象可能在 print-object 上定义了方法.例如,

(defclass x () ())

(defmethod print-object ((x x) stream)
  (format *error-output* "Executing arbitrary code...~%")
  (call-next-method x stream))

CL-USER> (format t "~A" (make-instance 'x))
Executing arbitrary code...
#<X {1004E4B513}>
NIL

我认为您需要自己定义哪些指令是 安全的,使用您认为重要的任何标准,然后仅包括那些。

如果用户不能引入自定义代码而只是格式化字符串,那么就可以避免print-object的问题。请记住使用 with-standard-io-syntax(或它的自定义版本)来控制您将生成的确切输出类型(想想 *print-base*,...)。

您可以扫描输入字符串以检测 ~/ 的存在(但 ~~/ 有效)并拒绝解释包含列入黑名单的结构的格式。 但是,有些分析比较困难,您可能需要在运行时进行操作。

例如,如果格式字符串格式不正确,您可能会遇到必须处理的错误(此外,您可能会为预期的参数提供错误的值)。

即使用户没有恶意,您也可能会遇到迭代构造问题:

~{<X>~:*~}

... 永远不会停止,因为 ~:* 倒回当前参数。为了处理这个问题,您必须考虑 <X> 可能会或不会打印某些内容。您可以实施这两种策略:

  • 有一个超时来限制格式化所花费的时间
  • 当写入过多时(例如写入字符串缓冲区),让底层流到达文件末尾。

可能还有其他我目前没有发现的问题,请注意。