From 63922942ea1eb76534810686b711a550e3745c57 Mon Sep 17 00:00:00 2001 From: aleteoryx Date: Sat, 9 Nov 2024 17:55:45 -0500 Subject: [PATCH] plugin version comparison, improved manifest reading --- migrate_core.tcl | 12 ++-- plugins.tcl | 125 +++++++++++++++++++++++++++++++++++----- testplugin/manifest.tcl | 1 + 3 files changed, 119 insertions(+), 19 deletions(-) diff --git a/migrate_core.tcl b/migrate_core.tcl index fe0663c..d2edaa7 100644 --- a/migrate_core.tcl +++ b/migrate_core.tcl @@ -5,11 +5,13 @@ interp create migrator migrator eval { lappend migrations 0 {init plugins} { -- init plugins -CREATE TABLE plugins (slug TEXT PRIMARY KEY, - version INTEGER, - hashes TEXT, - trusted BOOL, - priority INTEGER); +CREATE TABLE plugins (slug TEXT NOT NULL, -- corresponds directly to manifest + namespace TEXT NOT NULL, -- corresponds directly to manifest + version TEXT NOT NULL, -- corresponds directly to manifest + hashes TEXT NOT NULL, -- dict of path -> hash + trusted BOOL NOT NULL, -- bypass security checks for plugins with this name + priority INTEGER NOT NULL, -- load priority + PRIMARY KEY (slug, namespace)); } } diff --git a/plugins.tcl b/plugins.tcl index bea1240..39b8495 100644 --- a/plugins.tcl +++ b/plugins.tcl @@ -5,6 +5,8 @@ package require sha256 # - name: string # - slug: string # must match /^[-_a-zA-Z0-9]+$/ +# - namespace: string +# must match /^[-_a-zA-Z0-9]+$/ # - version: string[] # ordered like {1} > {0}, {0 1} < {1 0}. {1 1} > {1} # may set: @@ -24,10 +26,85 @@ namespace eval plugins { variable plugins variable log [logger::init tclircc::plugins] + # generate the script to exfiltrate data from the manifest + variable mf_required { + name 1 + namespace {[regexp {^[_a-zA-Z0-9-]+$} $val]} + slug {[regexp {^[_a-zA-Z0-9-]+$} $val]} + version {$val != ""} } + variable mf_optional { + description 1 + author 1 + license 1 } + variable mf_procs { + + } + variable manifest_reader { + set manifest_dict {} + } + foreach {key check} $mf_required { + set chunk { + if {[info exists %key]} { + set val [set %key] + if %check { + dict set manifest_dict %key [set %key] + } else { return -code error "invalid value for %key: $val" } + } else { return -code error "missing key in manifest: %key" } + } + + regsub -all "%key" $chunk [list $key] chunk + regsub -all "%check" $chunk [list $check] chunk + + append manifest_reader $chunk + } + foreach {key check} $mf_optional { + set chunk { + if {[info exists %key]} { + set val [set %key] + if %check { + dict set manifest_dict %key [set %key] + } + } + } + + regsub -all "%key" $chunk [list $key] chunk + regsub -all "%check" $chunk [list $check] chunk + + append manifest_reader $chunk + } + foreach {key _} $mf_procs { + set chunk { + if {%key in [info procs %key]} { + dict set manifest_dict procs %key [list [info args %key] [info body %key]] + } + } + + regsub -all "%key" $chunk [list $key] chunk + + append manifest_reader $chunk + } + append manifest_reader { set manifest_dict } + # generation done + + proc vercmp {v1 v2} { + # normalize the lists, check for equality + if {[list {*}$v1] == [list {*}$v2]} { return 0 } + # compare every element + for {set n 0} {$n < min([llength $v1], [llength $v2])} {incr n} { + if [set cmp [string compare [lindex $v1 $n] [lindex $v2 $n]]] { + return $cmp + } + } + # they can't be equal, so the longer one must be the newer one + if {[llength $v1] > [llength $v2]} { return 1 } + return -1 + } + proc enroll_interp {tid iid} {} - proc load {dir} { + proc load {dir {allow_internal 0}} { variable log + variable manifest_reader ${log}::info "attempting to load plugin from \"$dir\"..." @@ -43,6 +120,7 @@ namespace eval plugins { interp create -safe mf_exec # prevent escapes interp hide mf_exec package + interp hide mf_exec interp # prevent hanging the thread interp hide mf_exec vwait interp hide mf_exec after @@ -51,7 +129,7 @@ namespace eval plugins { 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 time -seconds [expr {[clock seconds] + 2}] interp limit mf_exec command -value 10000 # untrusted code time @@ -63,22 +141,18 @@ namespace eval plugins { 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" + set mf_valid [catch { interp eval mf_exec $manifest_reader } result opts] + if {$mf_valid != 0} { + ${log}::error "couldn't validate plugin: $result" 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 .]" + # hooray, we have the manifest! + set manifest $result + interp delete mf_exec } result opts] switch -- $untrusted_result { - 0 { return result } 2 { return result } + 0 { } 2 { return -code 2 result } 1 { ${log}::error "couldn't load plugin: $result" return 0 @@ -86,9 +160,32 @@ namespace eval plugins { default { ${log}::alert "unexpected return code from plugin handling: $untrusted_result" ${log}::alert "return options: $opts" - ${log}::alert "THIS MAY INDICATE A SANDBOX COMPROMISE!" + ${log}::alert "THIS MAY INDICATE A PLUGIN SANDBOX COMPROMISE!" return 0 } } + + set pl_name [dict get $manifest name] + set pl_namespace [dict get $manifest namespace] + set pl_slug [dict get $manifest slug] + set pl_version [dict get $manifest version] + + ${log}::debug "manifest loaded: ${pl_name} (${pl_namespace}::${pl_slug}) v[join ${pl_version} .]" + + set stored [core_db eval { + SELECT hashes, version, trusted FROM plugins + WHERE slug = $pl_slug + AND namespace = $pl_namespace}] + + if ![llength $stored] { + puts "new plugin" + } else { + lassign $stored st_hashes st_version st_trusted + switch -- [vercmp $pl_version $st_version] { + 0 { puts "same version" } + -1 { puts "older version" } + 1 { puts "newer version" } + } + } } } diff --git a/testplugin/manifest.tcl b/testplugin/manifest.tcl index 848c682..8440ed8 100644 --- a/testplugin/manifest.tcl +++ b/testplugin/manifest.tcl @@ -1,3 +1,4 @@ set name "test plugin" set slug "test" +set namespace "aleteoryx" set version {0 0 1} -- 2.45.2