~aleteoryx/tclircc

ref: 5363eb2f1e00b84914e479c9b330d66449a3dcaf tclircc/plugins.tcl -rw-r--r-- 3.0 KiB
5363eb2faleteoryx various things, i found a uaf in the core distro a month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package require sha256

# plugin manifest format:
# tcl script that must set the following variables at a minimum:
# - name: string
# - slug: string
#   must match /^[-_a-zA-Z0-9]+$/
# - version: string[]
#   ordered like {1} > {0}, {0 1} < {1 0}. {1 1} > {1}
# may set:
# - description: string
# - author: string[]
#   should contain a list of names with optional website or email, e.g.
#   {{Aleteoryx <https://aleteoryx.me>}
#    {Alice P Hacker <aph@example.com>}}
# - license: string
#   may begin with spdx: to indicate an spdx id or file: to indicate
#   a file path relative to the plugin root. .. and root references are
#   stripped. if neither prefix applies, assumed to be literal license
#   text.


namespace eval plugins {
  variable plugins
  variable log [logger::init tclircc::plugins]

  proc enroll_interp {tid iid} {}

  proc load {dir} {
    variable log

    ${log}::info "attempting to load plugin from \"$dir\"..."

    set mf_path [file join $dir manifest.tcl]
    if {!([file isfile $mf_path] && [file readable $mf_path])} {
      ${log}::error "couldn't load plugin: manifest \"$mf_path\" not present or not readable!"
      return 0
    }
    set mf_fd [open $mf_path]
    set manifest [read $mf_fd]
    close $mf_fd

    interp create -safe mf_exec
    # prevent escapes
    interp hide mf_exec package
    # prevent hanging the thread
    interp hide mf_exec vwait
    interp hide mf_exec after
    interp hide mf_exec update
    interp hide mf_exec gets
    interp hide mf_exec read

    # this is more than enough for any reasonable manifest
    interp limit mf_exec time -seconds [expr {[clock seconds] + 1}]
    interp limit mf_exec command -value 10000

    # untrusted code time
    ${log}::debug "evaluating manifest..."
    set untrusted_result [catch {
      set mf_result [catch { interp eval mf_exec $manifest } result opts]
      if {$mf_result != 0} {
        ${log}::error "couldn't load plugin: manifest return code $mf_result: $result"
        return 0
      }

      set has_required [interp eval mf_exec {expr {[info exists name] &&
                                                   [info exists slug] &&
                                                   [info exists version]}}]
      if {!$has_required} {
        ${log}::error "couldn't load plugin: missing required values in manifest"
        return 0
      }

      set name [interp eval mf_exec {set name}]
      set slug [interp eval mf_exec {set slug}]
      set version [interp eval mf_exec {set version}]

      ${log}::debug "manifest loaded: $name ($slug) v[join $version .]"
    } result opts]
    switch -- $untrusted_result {
      0 { return result } 2 { return result }
      1 {
        ${log}::error "couldn't load plugin: $result"
        return 0
      }
      default {
        ${log}::alert "unexpected return code from plugin handling: $untrusted_result"
        ${log}::alert "return options: $opts"
        ${log}::alert "THIS MAY INDICATE A SANDBOX COMPROMISE!"
        return 0
      }
    }
  }
}