safe.tcl 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289
  1. # safe.tcl --
  2. #
  3. # This file provide a safe loading/sourcing mechanism for safe interpreters.
  4. # It implements a virtual path mechanism to hide the real pathnames from the
  5. # child. It runs in a parent interpreter and sets up data structure and
  6. # aliases that will be invoked when used from a child interpreter.
  7. #
  8. # See the safe.n man page for details.
  9. #
  10. # Copyright (c) 1996-1997 Sun Microsystems, Inc.
  11. #
  12. # See the file "license.terms" for information on usage and redistribution of
  13. # this file, and for a DISCLAIMER OF ALL WARRANTIES.
  14. #
  15. # The implementation is based on namespaces. These naming conventions are
  16. # followed:
  17. # Private procs starts with uppercase.
  18. # Public procs are exported and starts with lowercase
  19. #
  20. # Needed utilities package
  21. package require opt 0.4.8
  22. # Create the safe namespace
  23. namespace eval ::safe {
  24. # Exported API:
  25. namespace export interpCreate interpInit interpConfigure interpDelete \
  26. interpAddToAccessPath interpFindInAccessPath setLogCmd
  27. }
  28. # Helper function to resolve the dual way of specifying staticsok (either
  29. # by -noStatics or -statics 0)
  30. proc ::safe::InterpStatics {} {
  31. foreach v {Args statics noStatics} {
  32. upvar $v $v
  33. }
  34. set flag [::tcl::OptProcArgGiven -noStatics]
  35. if {$flag && (!$noStatics == !$statics)
  36. && ([::tcl::OptProcArgGiven -statics])} {
  37. return -code error\
  38. "conflicting values given for -statics and -noStatics"
  39. }
  40. if {$flag} {
  41. return [expr {!$noStatics}]
  42. } else {
  43. return $statics
  44. }
  45. }
  46. # Helper function to resolve the dual way of specifying nested loading
  47. # (either by -nestedLoadOk or -nested 1)
  48. proc ::safe::InterpNested {} {
  49. foreach v {Args nested nestedLoadOk} {
  50. upvar $v $v
  51. }
  52. set flag [::tcl::OptProcArgGiven -nestedLoadOk]
  53. # note that the test here is the opposite of the "InterpStatics" one
  54. # (it is not -noNested... because of the wanted default value)
  55. if {$flag && (!$nestedLoadOk != !$nested)
  56. && ([::tcl::OptProcArgGiven -nested])} {
  57. return -code error\
  58. "conflicting values given for -nested and -nestedLoadOk"
  59. }
  60. if {$flag} {
  61. # another difference with "InterpStatics"
  62. return $nestedLoadOk
  63. } else {
  64. return $nested
  65. }
  66. }
  67. ####
  68. #
  69. # API entry points that needs argument parsing :
  70. #
  71. ####
  72. # Interface/entry point function and front end for "Create"
  73. proc ::safe::interpCreate {args} {
  74. set Args [::tcl::OptKeyParse ::safe::interpCreate $args]
  75. RejectExcessColons $slave
  76. InterpCreate $slave $accessPath \
  77. [InterpStatics] [InterpNested] $deleteHook
  78. }
  79. proc ::safe::interpInit {args} {
  80. set Args [::tcl::OptKeyParse ::safe::interpIC $args]
  81. if {![::interp exists $slave]} {
  82. return -code error "\"$slave\" is not an interpreter"
  83. }
  84. RejectExcessColons $slave
  85. InterpInit $slave $accessPath \
  86. [InterpStatics] [InterpNested] $deleteHook
  87. }
  88. # Check that the given child is "one of us"
  89. proc ::safe::CheckInterp {child} {
  90. namespace upvar ::safe [VarName $child] state
  91. if {![info exists state] || ![::interp exists $child]} {
  92. return -code error \
  93. "\"$child\" is not an interpreter managed by ::safe::"
  94. }
  95. }
  96. # Interface/entry point function and front end for "Configure". This code
  97. # is awfully pedestrian because it would need more coupling and support
  98. # between the way we store the configuration values in safe::interp's and
  99. # the Opt package. Obviously we would like an OptConfigure to avoid
  100. # duplicating all this code everywhere.
  101. # -> TODO (the app should share or access easily the program/value stored
  102. # by opt)
  103. # This is even more complicated by the boolean flags with no values that
  104. # we had the bad idea to support for the sake of user simplicity in
  105. # create/init but which makes life hard in configure...
  106. # So this will be hopefully written and some integrated with opt1.0
  107. # (hopefully for tcl8.1 ?)
  108. proc ::safe::interpConfigure {args} {
  109. switch [llength $args] {
  110. 1 {
  111. # If we have exactly 1 argument the semantic is to return all
  112. # the current configuration. We still call OptKeyParse though
  113. # we know that "child" is our given argument because it also
  114. # checks for the "-help" option.
  115. set Args [::tcl::OptKeyParse ::safe::interpIC $args]
  116. CheckInterp $slave
  117. namespace upvar ::safe [VarName $slave] state
  118. return [join [list \
  119. [list -accessPath $state(access_path)] \
  120. [list -statics $state(staticsok)] \
  121. [list -nested $state(nestedok)] \
  122. [list -deleteHook $state(cleanupHook)]]]
  123. }
  124. 2 {
  125. # If we have exactly 2 arguments the semantic is a "configure
  126. # get"
  127. lassign $args slave arg
  128. # get the flag sub program (we 'know' about Opt's internal
  129. # representation of data)
  130. set desc [lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2]
  131. set hits [::tcl::OptHits desc $arg]
  132. if {$hits > 1} {
  133. return -code error [::tcl::OptAmbigous $desc $arg]
  134. } elseif {$hits == 0} {
  135. return -code error [::tcl::OptFlagUsage $desc $arg]
  136. }
  137. CheckInterp $slave
  138. namespace upvar ::safe [VarName $slave] state
  139. set item [::tcl::OptCurDesc $desc]
  140. set name [::tcl::OptName $item]
  141. switch -exact -- $name {
  142. -accessPath {
  143. return [list -accessPath $state(access_path)]
  144. }
  145. -statics {
  146. return [list -statics $state(staticsok)]
  147. }
  148. -nested {
  149. return [list -nested $state(nestedok)]
  150. }
  151. -deleteHook {
  152. return [list -deleteHook $state(cleanupHook)]
  153. }
  154. -noStatics {
  155. # it is most probably a set in fact but we would need
  156. # then to jump to the set part and it is not *sure*
  157. # that it is a set action that the user want, so force
  158. # it to use the unambigous -statics ?value? instead:
  159. return -code error\
  160. "ambigous query (get or set -noStatics ?)\
  161. use -statics instead"
  162. }
  163. -nestedLoadOk {
  164. return -code error\
  165. "ambigous query (get or set -nestedLoadOk ?)\
  166. use -nested instead"
  167. }
  168. default {
  169. return -code error "unknown flag $name (bug)"
  170. }
  171. }
  172. }
  173. default {
  174. # Otherwise we want to parse the arguments like init and
  175. # create did
  176. set Args [::tcl::OptKeyParse ::safe::interpIC $args]
  177. CheckInterp $slave
  178. namespace upvar ::safe [VarName $slave] state
  179. # Get the current (and not the default) values of whatever has
  180. # not been given:
  181. if {![::tcl::OptProcArgGiven -accessPath]} {
  182. set doreset 0
  183. set accessPath $state(access_path)
  184. } else {
  185. set doreset 1
  186. }
  187. if {
  188. ![::tcl::OptProcArgGiven -statics]
  189. && ![::tcl::OptProcArgGiven -noStatics]
  190. } then {
  191. set statics $state(staticsok)
  192. } else {
  193. set statics [InterpStatics]
  194. }
  195. if {
  196. [::tcl::OptProcArgGiven -nested] ||
  197. [::tcl::OptProcArgGiven -nestedLoadOk]
  198. } then {
  199. set nested [InterpNested]
  200. } else {
  201. set nested $state(nestedok)
  202. }
  203. if {![::tcl::OptProcArgGiven -deleteHook]} {
  204. set deleteHook $state(cleanupHook)
  205. }
  206. # we can now reconfigure :
  207. InterpSetConfig $slave $accessPath $statics $nested $deleteHook
  208. # auto_reset the child (to completly synch the new access_path)
  209. if {$doreset} {
  210. if {[catch {::interp eval $slave {auto_reset}} msg]} {
  211. Log $slave "auto_reset failed: $msg"
  212. } else {
  213. Log $slave "successful auto_reset" NOTICE
  214. }
  215. # Sync the paths used to search for Tcl modules.
  216. ::interp eval $slave {tcl::tm::path remove {*}[tcl::tm::list]}
  217. if {[llength $state(tm_path_slave)] > 0} {
  218. ::interp eval $slave [list \
  219. ::tcl::tm::add {*}[lreverse $state(tm_path_slave)]]
  220. }
  221. # Remove stale "package ifneeded" data for non-loaded packages.
  222. # - Not for loaded packages, because "package forget" erases
  223. # data from "package provide" as well as "package ifneeded".
  224. # - This is OK because the script cannot reload any version of
  225. # the package unless it first does "package forget".
  226. foreach pkg [::interp eval $slave {package names}] {
  227. if {[::interp eval $slave [list package provide $pkg]] eq ""} {
  228. ::interp eval $slave [list package forget $pkg]
  229. }
  230. }
  231. }
  232. return
  233. }
  234. }
  235. }
  236. ####
  237. #
  238. # Functions that actually implements the exported APIs
  239. #
  240. ####
  241. #
  242. # safe::InterpCreate : doing the real job
  243. #
  244. # This procedure creates a safe interpreter and initializes it with the safe
  245. # base aliases.
  246. # NB: child name must be simple alphanumeric string, no spaces, no (), no
  247. # {},... {because the state array is stored as part of the name}
  248. #
  249. # Returns the child name.
  250. #
  251. # Optional Arguments :
  252. # + child name : if empty, generated name will be used
  253. # + access_path: path list controlling where load/source can occur,
  254. # if empty: the parent auto_path will be used.
  255. # + staticsok : flag, if 0 :no static package can be loaded (load {} Xxx)
  256. # if 1 :static packages are ok.
  257. # + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub)
  258. # if 1 : multiple levels are ok.
  259. # use the full name and no indent so auto_mkIndex can find us
  260. proc ::safe::InterpCreate {
  261. child
  262. access_path
  263. staticsok
  264. nestedok
  265. deletehook
  266. } {
  267. # Create the child.
  268. # If evaluated in ::safe, the interpreter command for foo is ::foo;
  269. # but for foo::bar is safe::foo::bar. So evaluate in :: instead.
  270. if {$child ne ""} {
  271. namespace eval :: [list ::interp create -safe $child]
  272. } else {
  273. # empty argument: generate child name
  274. set child [::interp create -safe]
  275. }
  276. Log $child "Created" NOTICE
  277. # Initialize it. (returns child name)
  278. InterpInit $child $access_path $staticsok $nestedok $deletehook
  279. }
  280. #
  281. # InterpSetConfig (was setAccessPath) :
  282. # Sets up child virtual auto_path and corresponding structure within
  283. # the parent. Also sets the tcl_library in the child to be the first
  284. # directory in the path.
  285. # NB: If you change the path after the child has been initialized you
  286. # probably need to call "auto_reset" in the child in order that it gets
  287. # the right auto_index() array values.
  288. proc ::safe::InterpSetConfig {child access_path staticsok nestedok deletehook} {
  289. global auto_path
  290. # determine and store the access path if empty
  291. if {$access_path eq ""} {
  292. set access_path $auto_path
  293. # Make sure that tcl_library is in auto_path and at the first
  294. # position (needed by setAccessPath)
  295. set where [lsearch -exact $access_path [info library]]
  296. if {$where < 0} {
  297. # not found, add it.
  298. set access_path [linsert $access_path 0 [info library]]
  299. Log $child "tcl_library was not in auto_path,\
  300. added it to slave's access_path" NOTICE
  301. } elseif {$where != 0} {
  302. # not first, move it first
  303. set access_path [linsert \
  304. [lreplace $access_path $where $where] \
  305. 0 [info library]]
  306. Log $child "tcl_libray was not in first in auto_path,\
  307. moved it to front of slave's access_path" NOTICE
  308. }
  309. # Add 1st level sub dirs (will searched by auto loading from tcl
  310. # code in the child using glob and thus fail, so we add them here
  311. # so by default it works the same).
  312. set access_path [AddSubDirs $access_path]
  313. }
  314. Log $child "Setting accessPath=($access_path) staticsok=$staticsok\
  315. nestedok=$nestedok deletehook=($deletehook)" NOTICE
  316. namespace upvar ::safe [VarName $child] state
  317. # clear old autopath if it existed
  318. # build new one
  319. # Extend the access list with the paths used to look for Tcl Modules.
  320. # We save the virtual form separately as well, as syncing it with the
  321. # child has to be deferred until the necessary commands are present for
  322. # setup.
  323. set norm_access_path {}
  324. set slave_access_path {}
  325. set map_access_path {}
  326. set remap_access_path {}
  327. set slave_tm_path {}
  328. set i 0
  329. foreach dir $access_path {
  330. set token [PathToken $i]
  331. lappend slave_access_path $token
  332. lappend map_access_path $token $dir
  333. lappend remap_access_path $dir $token
  334. lappend norm_access_path [file normalize $dir]
  335. incr i
  336. }
  337. set morepaths [::tcl::tm::list]
  338. set firstpass 1
  339. while {[llength $morepaths]} {
  340. set addpaths $morepaths
  341. set morepaths {}
  342. foreach dir $addpaths {
  343. # Prevent the addition of dirs on the tm list to the
  344. # result if they are already known.
  345. if {[dict exists $remap_access_path $dir]} {
  346. if {$firstpass} {
  347. # $dir is in [::tcl::tm::list] and belongs in the slave_tm_path.
  348. # Later passes handle subdirectories, which belong in the
  349. # access path but not in the module path.
  350. lappend slave_tm_path [dict get $remap_access_path $dir]
  351. }
  352. continue
  353. }
  354. set token [PathToken $i]
  355. lappend access_path $dir
  356. lappend slave_access_path $token
  357. lappend map_access_path $token $dir
  358. lappend remap_access_path $dir $token
  359. lappend norm_access_path [file normalize $dir]
  360. if {$firstpass} {
  361. # $dir is in [::tcl::tm::list] and belongs in the slave_tm_path.
  362. # Later passes handle subdirectories, which belong in the
  363. # access path but not in the module path.
  364. lappend slave_tm_path $token
  365. }
  366. incr i
  367. # [Bug 2854929]
  368. # Recursively find deeper paths which may contain
  369. # modules. Required to handle modules with names like
  370. # 'platform::shell', which translate into
  371. # 'platform/shell-X.tm', i.e arbitrarily deep
  372. # subdirectories.
  373. lappend morepaths {*}[glob -nocomplain -directory $dir -type d *]
  374. }
  375. set firstpass 0
  376. }
  377. set state(access_path) $access_path
  378. set state(access_path,map) $map_access_path
  379. set state(access_path,remap) $remap_access_path
  380. set state(access_path,norm) $norm_access_path
  381. set state(access_path,slave) $slave_access_path
  382. set state(tm_path_slave) $slave_tm_path
  383. set state(staticsok) $staticsok
  384. set state(nestedok) $nestedok
  385. set state(cleanupHook) $deletehook
  386. SyncAccessPath $child
  387. return
  388. }
  389. #
  390. #
  391. # FindInAccessPath:
  392. # Search for a real directory and returns its virtual Id (including the
  393. # "$")
  394. proc ::safe::interpFindInAccessPath {child path} {
  395. CheckInterp $child
  396. namespace upvar ::safe [VarName $child] state
  397. if {![dict exists $state(access_path,remap) $path]} {
  398. return -code error "$path not found in access path"
  399. }
  400. return [dict get $state(access_path,remap) $path]
  401. }
  402. #
  403. # addToAccessPath:
  404. # add (if needed) a real directory to access path and return its
  405. # virtual token (including the "$").
  406. proc ::safe::interpAddToAccessPath {child path} {
  407. # first check if the directory is already in there
  408. # (inlined interpFindInAccessPath).
  409. CheckInterp $child
  410. namespace upvar ::safe [VarName $child] state
  411. if {[dict exists $state(access_path,remap) $path]} {
  412. return [dict get $state(access_path,remap) $path]
  413. }
  414. # new one, add it:
  415. set token [PathToken [llength $state(access_path)]]
  416. lappend state(access_path) $path
  417. lappend state(access_path,slave) $token
  418. lappend state(access_path,map) $token $path
  419. lappend state(access_path,remap) $path $token
  420. lappend state(access_path,norm) [file normalize $path]
  421. SyncAccessPath $child
  422. return $token
  423. }
  424. # This procedure applies the initializations to an already existing
  425. # interpreter. It is useful when you want to install the safe base aliases
  426. # into a preexisting safe interpreter.
  427. proc ::safe::InterpInit {
  428. child
  429. access_path
  430. staticsok
  431. nestedok
  432. deletehook
  433. } {
  434. # Configure will generate an access_path when access_path is empty.
  435. InterpSetConfig $child $access_path $staticsok $nestedok $deletehook
  436. # NB we need to add [namespace current], aliases are always absolute
  437. # paths.
  438. # These aliases let the child load files to define new commands
  439. # This alias lets the child use the encoding names, convertfrom,
  440. # convertto, and system, but not "encoding system <name>" to set the
  441. # system encoding.
  442. # Handling Tcl Modules, we need a restricted form of Glob.
  443. # This alias interposes on the 'exit' command and cleanly terminates
  444. # the child.
  445. foreach {command alias} {
  446. source AliasSource
  447. load AliasLoad
  448. encoding AliasEncoding
  449. exit interpDelete
  450. glob AliasGlob
  451. } {
  452. ::interp alias $child $command {} [namespace current]::$alias $child
  453. }
  454. # This alias lets the child have access to a subset of the 'file'
  455. # command functionality.
  456. ::interp expose $child file
  457. foreach subcommand {dirname extension rootname tail} {
  458. ::interp alias $child ::tcl::file::$subcommand {} \
  459. ::safe::AliasFileSubcommand $child $subcommand
  460. }
  461. foreach subcommand {
  462. atime attributes copy delete executable exists isdirectory isfile
  463. link lstat mtime mkdir nativename normalize owned readable readlink
  464. rename size stat tempfile type volumes writable
  465. } {
  466. ::interp alias $child ::tcl::file::$subcommand {} \
  467. ::safe::BadSubcommand $child file $subcommand
  468. }
  469. # Subcommands of info
  470. foreach {subcommand alias} {
  471. nameofexecutable AliasExeName
  472. } {
  473. ::interp alias $child ::tcl::info::$subcommand \
  474. {} [namespace current]::$alias $child
  475. }
  476. # The allowed child variables already have been set by Tcl_MakeSafe(3)
  477. # Source init.tcl and tm.tcl into the child, to get auto_load and
  478. # other procedures defined:
  479. if {[catch {::interp eval $child {
  480. source [file join $tcl_library init.tcl]
  481. }} msg opt]} {
  482. Log $child "can't source init.tcl ($msg)"
  483. return -options $opt "can't source init.tcl into slave $child ($msg)"
  484. }
  485. if {[catch {::interp eval $child {
  486. source [file join $tcl_library tm.tcl]
  487. }} msg opt]} {
  488. Log $child "can't source tm.tcl ($msg)"
  489. return -options $opt "can't source tm.tcl into slave $child ($msg)"
  490. }
  491. # Sync the paths used to search for Tcl modules. This can be done only
  492. # now, after tm.tcl was loaded.
  493. namespace upvar ::safe [VarName $child] state
  494. if {[llength $state(tm_path_slave)] > 0} {
  495. ::interp eval $child [list \
  496. ::tcl::tm::add {*}[lreverse $state(tm_path_slave)]]
  497. }
  498. return $child
  499. }
  500. # Add (only if needed, avoid duplicates) 1 level of sub directories to an
  501. # existing path list. Also removes non directories from the returned
  502. # list.
  503. proc ::safe::AddSubDirs {pathList} {
  504. set res {}
  505. foreach dir $pathList {
  506. if {[file isdirectory $dir]} {
  507. # check that we don't have it yet as a children of a previous
  508. # dir
  509. if {$dir ni $res} {
  510. lappend res $dir
  511. }
  512. foreach sub [glob -directory $dir -nocomplain *] {
  513. if {[file isdirectory $sub] && ($sub ni $res)} {
  514. # new sub dir, add it !
  515. lappend res $sub
  516. }
  517. }
  518. }
  519. }
  520. return $res
  521. }
  522. # This procedure deletes a safe interpreter managed by Safe Tcl and cleans up
  523. # associated state.
  524. # - The command will also delete non-Safe-Base interpreters.
  525. # - This is regrettable, but to avoid breaking existing code this should be
  526. # amended at the next major revision by uncommenting "CheckInterp".
  527. proc ::safe::interpDelete {child} {
  528. Log $child "About to delete" NOTICE
  529. # CheckInterp $child
  530. namespace upvar ::safe [VarName $child] state
  531. # When an interpreter is deleted with [interp delete], any sub-interpreters
  532. # are deleted automatically, but this leaves behind their data in the Safe
  533. # Base. To clean up properly, we call safe::interpDelete recursively on each
  534. # Safe Base sub-interpreter, so each one is deleted cleanly and not by
  535. # the automatic mechanism built into [interp delete].
  536. foreach sub [interp children $child] {
  537. if {[info exists ::safe::[VarName [list $child $sub]]]} {
  538. ::safe::interpDelete [list $child $sub]
  539. }
  540. }
  541. # If the child has a cleanup hook registered, call it. Check the
  542. # existance because we might be called to delete an interp which has
  543. # not been registered with us at all
  544. if {[info exists state(cleanupHook)]} {
  545. set hook $state(cleanupHook)
  546. if {[llength $hook]} {
  547. # remove the hook now, otherwise if the hook calls us somehow,
  548. # we'll loop
  549. unset state(cleanupHook)
  550. try {
  551. {*}$hook $child
  552. } on error err {
  553. Log $child "Delete hook error ($err)"
  554. }
  555. }
  556. }
  557. # Discard the global array of state associated with the child, and
  558. # delete the interpreter.
  559. if {[info exists state]} {
  560. unset state
  561. }
  562. # if we have been called twice, the interp might have been deleted
  563. # already
  564. if {[::interp exists $child]} {
  565. ::interp delete $child
  566. Log $child "Deleted" NOTICE
  567. }
  568. return
  569. }
  570. # Set (or get) the logging mecanism
  571. proc ::safe::setLogCmd {args} {
  572. variable Log
  573. set la [llength $args]
  574. if {$la == 0} {
  575. return $Log
  576. } elseif {$la == 1} {
  577. set Log [lindex $args 0]
  578. } else {
  579. set Log $args
  580. }
  581. if {$Log eq ""} {
  582. # Disable logging completely. Calls to it will be compiled out
  583. # of all users.
  584. proc ::safe::Log {args} {}
  585. } else {
  586. # Activate logging, define proper command.
  587. proc ::safe::Log {child msg {type ERROR}} {
  588. variable Log
  589. {*}$Log "$type for slave $child : $msg"
  590. return
  591. }
  592. }
  593. }
  594. # ------------------- END OF PUBLIC METHODS ------------
  595. #
  596. # Sets the child auto_path to the parent recorded value. Also sets
  597. # tcl_library to the first token of the virtual path.
  598. #
  599. proc ::safe::SyncAccessPath {child} {
  600. namespace upvar ::safe [VarName $child] state
  601. set slave_access_path $state(access_path,slave)
  602. ::interp eval $child [list set auto_path $slave_access_path]
  603. Log $child "auto_path in $child has been set to $slave_access_path"\
  604. NOTICE
  605. # This code assumes that info library is the first element in the
  606. # list of auto_path's. See -> InterpSetConfig for the code which
  607. # ensures this condition.
  608. ::interp eval $child [list \
  609. set tcl_library [lindex $slave_access_path 0]]
  610. }
  611. # Returns the virtual token for directory number N.
  612. proc ::safe::PathToken {n} {
  613. # We need to have a ":" in the token string so [file join] on the
  614. # mac won't turn it into a relative path.
  615. return "\$p(:$n:)" ;# Form tested by case 7.2
  616. }
  617. #
  618. # translate virtual path into real path
  619. #
  620. proc ::safe::TranslatePath {child path} {
  621. namespace upvar ::safe [VarName $child] state
  622. # somehow strip the namespaces 'functionality' out (the danger is that
  623. # we would strip valid macintosh "../" queries... :
  624. if {[string match "*::*" $path] || [string match "*..*" $path]} {
  625. return -code error "invalid characters in path $path"
  626. }
  627. # Use a cached map instead of computed local vars and subst.
  628. return [string map $state(access_path,map) $path]
  629. }
  630. # file name control (limit access to files/resources that should be a
  631. # valid tcl source file)
  632. proc ::safe::CheckFileName {child file} {
  633. # This used to limit what can be sourced to ".tcl" and forbid files
  634. # with more than 1 dot and longer than 14 chars, but I changed that
  635. # for 8.4 as a safe interp has enough internal protection already to
  636. # allow sourcing anything. - hobbs
  637. if {![file exists $file]} {
  638. # don't tell the file path
  639. return -code error "no such file or directory"
  640. }
  641. if {![file readable $file]} {
  642. # don't tell the file path
  643. return -code error "not readable"
  644. }
  645. }
  646. # AliasFileSubcommand handles selected subcommands of [file] in safe
  647. # interpreters that are *almost* safe. In particular, it just acts to
  648. # prevent discovery of what home directories exist.
  649. proc ::safe::AliasFileSubcommand {child subcommand name} {
  650. if {[string match ~* $name]} {
  651. set name ./$name
  652. }
  653. tailcall ::interp invokehidden $child tcl:file:$subcommand $name
  654. }
  655. # AliasGlob is the target of the "glob" alias in safe interpreters.
  656. proc ::safe::AliasGlob {child args} {
  657. Log $child "GLOB ! $args" NOTICE
  658. set cmd {}
  659. set at 0
  660. array set got {
  661. -directory 0
  662. -nocomplain 0
  663. -join 0
  664. -tails 0
  665. -- 0
  666. }
  667. if {$::tcl_platform(platform) eq "windows"} {
  668. set dirPartRE {^(.*)[\\/]([^\\/]*)$}
  669. } else {
  670. set dirPartRE {^(.*)/([^/]*)$}
  671. }
  672. set dir {}
  673. set virtualdir {}
  674. while {$at < [llength $args]} {
  675. switch -glob -- [set opt [lindex $args $at]] {
  676. -nocomplain - -- - -tails {
  677. lappend cmd $opt
  678. set got($opt) 1
  679. incr at
  680. }
  681. -join {
  682. set got($opt) 1
  683. incr at
  684. }
  685. -types - -type {
  686. lappend cmd -types [lindex $args [incr at]]
  687. incr at
  688. }
  689. -directory {
  690. if {$got($opt)} {
  691. return -code error \
  692. {"-directory" cannot be used with "-path"}
  693. }
  694. set got($opt) 1
  695. set virtualdir [lindex $args [incr at]]
  696. incr at
  697. }
  698. -* {
  699. Log $child "Safe base rejecting glob option '$opt'"
  700. return -code error "Safe base rejecting glob option '$opt'"
  701. }
  702. default {
  703. break
  704. }
  705. }
  706. if {$got(--)} break
  707. }
  708. # Get the real path from the virtual one and check that the path is in the
  709. # access path of that child. Done after basic argument processing so that
  710. # we know if -nocomplain is set.
  711. if {$got(-directory)} {
  712. try {
  713. set dir [TranslatePath $child $virtualdir]
  714. DirInAccessPath $child $dir
  715. } on error msg {
  716. Log $child $msg
  717. if {$got(-nocomplain)} return
  718. return -code error "permission denied"
  719. }
  720. if {$got(--)} {
  721. set cmd [linsert $cmd end-1 -directory $dir]
  722. } else {
  723. lappend cmd -directory $dir
  724. }
  725. } else {
  726. # The code after this "if ... else" block would conspire to return with
  727. # no results in this case, if it were allowed to proceed. Instead,
  728. # return now and reduce the number of cases to be considered later.
  729. Log $child {option -directory must be supplied}
  730. if {$got(-nocomplain)} return
  731. return -code error "permission denied"
  732. }
  733. # Apply the -join semantics ourselves.
  734. if {$got(-join)} {
  735. set args [lreplace $args $at end [join [lrange $args $at end] "/"]]
  736. }
  737. # Process the pattern arguments. If we've done a join there is only one
  738. # pattern argument.
  739. set firstPattern [llength $cmd]
  740. foreach opt [lrange $args $at end] {
  741. if {![regexp $dirPartRE $opt -> thedir thefile]} {
  742. set thedir .
  743. # The *.tm search comes here.
  744. }
  745. # "Special" treatment for (joined) argument {*/pkgIndex.tcl}.
  746. # Do the expansion of "*" here, and filter out any directories that are
  747. # not in the access path. The outcome is to lappend to cmd a path of
  748. # the form $virtualdir/subdir/pkgIndex.tcl for each subdirectory subdir,
  749. # after removing any subdir that are not in the access path.
  750. if {($thedir eq "*") && ($thefile eq "pkgIndex.tcl")} {
  751. set mapped 0
  752. foreach d [glob -directory [TranslatePath $child $virtualdir] \
  753. -types d -tails *] {
  754. catch {
  755. DirInAccessPath $child \
  756. [TranslatePath $child [file join $virtualdir $d]]
  757. lappend cmd [file join $d $thefile]
  758. set mapped 1
  759. }
  760. }
  761. if {$mapped} continue
  762. # Don't [continue] if */pkgIndex.tcl has no matches in the access
  763. # path. The pattern will now receive the same treatment as a
  764. # "non-special" pattern (and will fail because it includes a "*" in
  765. # the directory name).
  766. }
  767. # Any directory pattern that is not an exact (i.e. non-glob) match to a
  768. # directory in the access path will be rejected here.
  769. # - Rejections include any directory pattern that has glob matching
  770. # patterns "*", "?", backslashes, braces or square brackets, (UNLESS
  771. # it corresponds to a genuine directory name AND that directory is in
  772. # the access path).
  773. # - The only "special matching characters" that remain in patterns for
  774. # processing by glob are in the filename tail.
  775. # - [file join $anything ~${foo}] is ~${foo}, which is not an exact
  776. # match to any directory in the access path. Hence directory patterns
  777. # that begin with "~" are rejected here. Tests safe-16.[5-8] check
  778. # that "file join" remains as required and does not expand ~${foo}.
  779. # - Bug [3529949] relates to unwanted expansion of ~${foo} and this is
  780. # how the present code avoids the bug. All tests safe-16.* relate.
  781. try {
  782. DirInAccessPath $child [TranslatePath $child \
  783. [file join $virtualdir $thedir]]
  784. } on error msg {
  785. Log $child $msg
  786. if {$got(-nocomplain)} continue
  787. return -code error "permission denied"
  788. }
  789. lappend cmd $opt
  790. }
  791. Log $child "GLOB = $cmd" NOTICE
  792. if {$got(-nocomplain) && [llength $cmd] eq $firstPattern} {
  793. return
  794. }
  795. try {
  796. # >>>>>>>>>> HERE'S THE CALL TO SAFE INTERP GLOB <<<<<<<<<<
  797. # - Pattern arguments added to cmd have NOT been translated from tokens.
  798. # Only the virtualdir is translated (to dir).
  799. # - In the pkgIndex.tcl case, there is no "*" in the pattern arguments,
  800. # which are a list of names each with tail pkgIndex.tcl. The purpose
  801. # of the call to glob is to remove the names for which the file does
  802. # not exist.
  803. set entries [::interp invokehidden $child glob {*}$cmd]
  804. } on error msg {
  805. # This is the only place that a call with -nocomplain and no invalid
  806. # "dash-options" can return an error.
  807. Log $child $msg
  808. return -code error "script error"
  809. }
  810. Log $child "GLOB < $entries" NOTICE
  811. # Translate path back to what the child should see.
  812. set res {}
  813. set l [string length $dir]
  814. foreach p $entries {
  815. if {[string equal -length $l $dir $p]} {
  816. set p [string replace $p 0 [expr {$l-1}] $virtualdir]
  817. }
  818. lappend res $p
  819. }
  820. Log $child "GLOB > $res" NOTICE
  821. return $res
  822. }
  823. # AliasSource is the target of the "source" alias in safe interpreters.
  824. proc ::safe::AliasSource {child args} {
  825. set argc [llength $args]
  826. # Extended for handling of Tcl Modules to allow not only "source
  827. # filename", but "source -encoding E filename" as well.
  828. if {[lindex $args 0] eq "-encoding"} {
  829. incr argc -2
  830. set encoding [lindex $args 1]
  831. set at 2
  832. if {$encoding eq "identity"} {
  833. Log $child "attempt to use the identity encoding"
  834. return -code error "permission denied"
  835. }
  836. } else {
  837. set at 0
  838. set encoding {}
  839. }
  840. if {$argc != 1} {
  841. set msg "wrong # args: should be \"source ?-encoding E? fileName\""
  842. Log $child "$msg ($args)"
  843. return -code error $msg
  844. }
  845. set file [lindex $args $at]
  846. # get the real path from the virtual one.
  847. if {[catch {
  848. set realfile [TranslatePath $child $file]
  849. } msg]} {
  850. Log $child $msg
  851. return -code error "permission denied"
  852. }
  853. # check that the path is in the access path of that child
  854. if {[catch {
  855. FileInAccessPath $child $realfile
  856. } msg]} {
  857. Log $child $msg
  858. return -code error "permission denied"
  859. }
  860. # Check that the filename exists and is readable. If it is not, deliver
  861. # this -errorcode so that caller in tclPkgUnknown does not write a message
  862. # to tclLog. Has no effect on other callers of ::source, which are in
  863. # "package ifneeded" scripts.
  864. if {[catch {
  865. CheckFileName $child $realfile
  866. } msg]} {
  867. Log $child "$realfile:$msg"
  868. return -code error -errorcode {POSIX EACCES} $msg
  869. }
  870. # Passed all the tests, lets source it. Note that we do this all manually
  871. # because we want to control [info script] in the child so information
  872. # doesn't leak so much. [Bug 2913625]
  873. set old [::interp eval $child {info script}]
  874. set replacementMsg "script error"
  875. set code [catch {
  876. set f [open $realfile]
  877. fconfigure $f -eofchar \032
  878. if {$encoding ne ""} {
  879. fconfigure $f -encoding $encoding
  880. }
  881. set contents [read $f]
  882. close $f
  883. ::interp eval $child [list info script $file]
  884. } msg opt]
  885. if {$code == 0} {
  886. set code [catch {::interp eval $child $contents} msg opt]
  887. set replacementMsg $msg
  888. }
  889. catch {interp eval $child [list info script $old]}
  890. # Note that all non-errors are fine result codes from [source], so we must
  891. # take a little care to do it properly. [Bug 2923613]
  892. if {$code == 1} {
  893. Log $child $msg
  894. return -code error $replacementMsg
  895. }
  896. return -code $code -options $opt $msg
  897. }
  898. # AliasLoad is the target of the "load" alias in safe interpreters.
  899. proc ::safe::AliasLoad {child file args} {
  900. set argc [llength $args]
  901. if {$argc > 2} {
  902. set msg "load error: too many arguments"
  903. Log $child "$msg ($argc) {$file $args}"
  904. return -code error $msg
  905. }
  906. # package name (can be empty if file is not).
  907. set package [lindex $args 0]
  908. namespace upvar ::safe [VarName $child] state
  909. # Determine where to load. load use a relative interp path and {}
  910. # means self, so we can directly and safely use passed arg.
  911. set target [lindex $args 1]
  912. if {$target ne ""} {
  913. # we will try to load into a sub sub interp; check that we want to
  914. # authorize that.
  915. if {!$state(nestedok)} {
  916. Log $child "loading to a sub interp (nestedok)\
  917. disabled (trying to load $package to $target)"
  918. return -code error "permission denied (nested load)"
  919. }
  920. }
  921. # Determine what kind of load is requested
  922. if {$file eq ""} {
  923. # static package loading
  924. if {$package eq ""} {
  925. set msg "load error: empty filename and no package name"
  926. Log $child $msg
  927. return -code error $msg
  928. }
  929. if {!$state(staticsok)} {
  930. Log $child "static packages loading disabled\
  931. (trying to load $package to $target)"
  932. return -code error "permission denied (static package)"
  933. }
  934. } else {
  935. # file loading
  936. # get the real path from the virtual one.
  937. try {
  938. set file [TranslatePath $child $file]
  939. } on error msg {
  940. Log $child $msg
  941. return -code error "permission denied"
  942. }
  943. # check the translated path
  944. try {
  945. FileInAccessPath $child $file
  946. } on error msg {
  947. Log $child $msg
  948. return -code error "permission denied (path)"
  949. }
  950. }
  951. try {
  952. return [::interp invokehidden $child load $file $package $target]
  953. } on error msg {
  954. # Some packages return no error message.
  955. set msg0 "load of binary library for package $package failed"
  956. if {$msg eq {}} {
  957. set msg $msg0
  958. } else {
  959. set msg "$msg0: $msg"
  960. }
  961. Log $child $msg
  962. return -code error $msg
  963. }
  964. }
  965. # FileInAccessPath raises an error if the file is not found in the list of
  966. # directories contained in the (parent side recorded) child's access path.
  967. # the security here relies on "file dirname" answering the proper
  968. # result... needs checking ?
  969. proc ::safe::FileInAccessPath {child file} {
  970. namespace upvar ::safe [VarName $child] state
  971. set access_path $state(access_path)
  972. if {[file isdirectory $file]} {
  973. return -code error "\"$file\": is a directory"
  974. }
  975. set parent [file dirname $file]
  976. # Normalize paths for comparison since lsearch knows nothing of
  977. # potential pathname anomalies.
  978. set norm_parent [file normalize $parent]
  979. namespace upvar ::safe [VarName $child] state
  980. if {$norm_parent ni $state(access_path,norm)} {
  981. return -code error "\"$file\": not in access_path"
  982. }
  983. }
  984. proc ::safe::DirInAccessPath {child dir} {
  985. namespace upvar ::safe [VarName $child] state
  986. set access_path $state(access_path)
  987. if {[file isfile $dir]} {
  988. return -code error "\"$dir\": is a file"
  989. }
  990. # Normalize paths for comparison since lsearch knows nothing of
  991. # potential pathname anomalies.
  992. set norm_dir [file normalize $dir]
  993. namespace upvar ::safe [VarName $child] state
  994. if {$norm_dir ni $state(access_path,norm)} {
  995. return -code error "\"$dir\": not in access_path"
  996. }
  997. }
  998. # This procedure is used to report an attempt to use an unsafe member of an
  999. # ensemble command.
  1000. proc ::safe::BadSubcommand {child command subcommand args} {
  1001. set msg "not allowed to invoke subcommand $subcommand of $command"
  1002. Log $child $msg
  1003. return -code error -errorcode {TCL SAFE SUBCOMMAND} $msg
  1004. }
  1005. # AliasEncoding is the target of the "encoding" alias in safe interpreters.
  1006. proc ::safe::AliasEncoding {child option args} {
  1007. # Note that [encoding dirs] is not supported in safe children at all
  1008. set subcommands {convertfrom convertto names system}
  1009. try {
  1010. set option [tcl::prefix match -error [list -level 1 -errorcode \
  1011. [list TCL LOOKUP INDEX option $option]] $subcommands $option]
  1012. # Special case: [encoding system] ok, but [encoding system foo] not
  1013. if {$option eq "system" && [llength $args]} {
  1014. return -code error -errorcode {TCL WRONGARGS} \
  1015. "wrong # args: should be \"encoding system\""
  1016. }
  1017. } on error {msg options} {
  1018. Log $child $msg
  1019. return -options $options $msg
  1020. }
  1021. tailcall ::interp invokehidden $child encoding $option {*}$args
  1022. }
  1023. # Various minor hiding of platform features. [Bug 2913625]
  1024. proc ::safe::AliasExeName {child} {
  1025. return ""
  1026. }
  1027. # ------------------------------------------------------------------------------
  1028. # Using Interpreter Names with Namespace Qualifiers
  1029. # ------------------------------------------------------------------------------
  1030. # (1) We wish to preserve compatibility with existing code, in which Safe Base
  1031. # interpreter names have no namespace qualifiers.
  1032. # (2) safe::interpCreate and the rest of the Safe Base previously could not
  1033. # accept namespace qualifiers in an interpreter name.
  1034. # (3) The interp command will accept namespace qualifiers in an interpreter
  1035. # name, but accepts distinct interpreters that will have the same command
  1036. # name (e.g. foo, ::foo, and :::foo) (bug 66c2e8c974).
  1037. # (4) To satisfy these constraints, Safe Base interpreter names will be fully
  1038. # qualified namespace names with no excess colons and with the leading "::"
  1039. # omitted.
  1040. # (5) Trailing "::" implies a namespace tail {}, which interp reads as {{}}.
  1041. # Reject such names.
  1042. # (6) We could:
  1043. # (a) EITHER reject usable but non-compliant names (e.g. excess colons) in
  1044. # interpCreate, interpInit;
  1045. # (b) OR accept such names and then translate to a compliant name in every
  1046. # command.
  1047. # The problem with (b) is that the user will expect to use the name with the
  1048. # interp command and will find that it is not recognised.
  1049. # E.g "interpCreate ::foo" creates interpreter "foo", and the user's name
  1050. # "::foo" works with all the Safe Base commands, but "interp eval ::foo"
  1051. # fails.
  1052. # So we choose (a).
  1053. # (7) The command
  1054. # namespace upvar ::safe S$child state
  1055. # becomes
  1056. # namespace upvar ::safe [VarName $child] state
  1057. # ------------------------------------------------------------------------------
  1058. proc ::safe::RejectExcessColons {child} {
  1059. set stripped [regsub -all -- {:::*} $child ::]
  1060. if {[string range $stripped end-1 end] eq {::}} {
  1061. return -code error {interpreter name must not end in "::"}
  1062. }
  1063. if {$stripped ne $child} {
  1064. set msg {interpreter name has excess colons in namespace separators}
  1065. return -code error $msg
  1066. }
  1067. if {[string range $stripped 0 1] eq {::}} {
  1068. return -code error {interpreter name must not begin "::"}
  1069. }
  1070. return
  1071. }
  1072. proc ::safe::VarName {child} {
  1073. # return S$child
  1074. return S[string map {:: @N @ @A} $child]
  1075. }
  1076. proc ::safe::Setup {} {
  1077. ####
  1078. #
  1079. # Setup the arguments parsing
  1080. #
  1081. ####
  1082. # Share the descriptions
  1083. set temp [::tcl::OptKeyRegister {
  1084. {-accessPath -list {} "access path for the slave"}
  1085. {-noStatics "prevent loading of statically linked pkgs"}
  1086. {-statics true "loading of statically linked pkgs"}
  1087. {-nestedLoadOk "allow nested loading"}
  1088. {-nested false "nested loading"}
  1089. {-deleteHook -script {} "delete hook"}
  1090. }]
  1091. # create case (slave is optional)
  1092. ::tcl::OptKeyRegister {
  1093. {?slave? -name {} "name of the slave (optional)"}
  1094. } ::safe::interpCreate
  1095. # adding the flags sub programs to the command program (relying on Opt's
  1096. # internal implementation details)
  1097. lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp)
  1098. # init and configure (slave is needed)
  1099. ::tcl::OptKeyRegister {
  1100. {slave -name {} "name of the slave"}
  1101. } ::safe::interpIC
  1102. # adding the flags sub programs to the command program (relying on Opt's
  1103. # internal implementation details)
  1104. lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp)
  1105. # temp not needed anymore
  1106. ::tcl::OptKeyDelete $temp
  1107. ####
  1108. #
  1109. # Default: No logging.
  1110. #
  1111. ####
  1112. setLogCmd {}
  1113. # Log eventually.
  1114. # To enable error logging, set Log to {puts stderr} for instance,
  1115. # via setLogCmd.
  1116. return
  1117. }
  1118. namespace eval ::safe {
  1119. # internal variables
  1120. # Log command, set via 'setLogCmd'. Logging is disabled when empty.
  1121. variable Log {}
  1122. # The package maintains a state array per child interp under its
  1123. # control. The name of this array is S<interp-name>. This array is
  1124. # brought into scope where needed, using 'namespace upvar'. The S
  1125. # prefix is used to avoid that a child interp called "Log" smashes
  1126. # the "Log" variable.
  1127. #
  1128. # The array's elements are:
  1129. #
  1130. # access_path : List of paths accessible to the child.
  1131. # access_path,norm : Ditto, in normalized form.
  1132. # access_path,slave : Ditto, as the path tokens as seen by the child.
  1133. # access_path,map : dict ( token -> path )
  1134. # access_path,remap : dict ( path -> token )
  1135. # tm_path_slave : List of TM root directories, as tokens seen by the child.
  1136. # staticsok : Value of option -statics
  1137. # nestedok : Value of option -nested
  1138. # cleanupHook : Value of option -deleteHook
  1139. }
  1140. ::safe::Setup