This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
skwerlman's profile picture

Simulating Root

Started by skwerlman, 19 March 2014 - 05:01 AM
skwerlman #1
Posted 19 March 2014 - 06:01 AM
I basically want to trick an arbitrary program in an arbitrary path into thinking it is in the root folder when it is run through my program. I've thought about overriding the fs API, but I'm not sure how to proceed. Any advice?
CometWolf #2
Posted 19 March 2014 - 07:30 AM
Something like this should do the trick.


local newPath = "/fakeroot/"
local overrideFs= {}
for k,v in pairs _G.fs do
  overrideFs[k] = function(...)
    local args = {...}
    for i=1,#args do
      if k == "open" and i ~= 2 then
        args[i] = newPath..args[i]
      end
    end
    return v(unpack(args))
  end
end

setmetatable(getfenv(), {
  __index = function(t,k)
    if k == "fs" then
      return overrideFs
    end
    return _G[k]
  end
})
What im doing here is overriding every fs function, with a new function which adds the newPath to every argument and then calls the original function. Then i set the index metamethod to use the override table if fs is called. This is something i just put together on my phone, so i can't guarantee it'll work though :P/>/>

Edit: realized it would mess up fs.open. Fixed it.
Edited on 19 March 2014 - 08:45 AM
skwerlman #3
Posted 19 March 2014 - 07:34 AM
So if someone calls fs.open("/file") it'll actually open "/fakeroot/file"?
Edited on 19 March 2014 - 06:35 AM
CometWolf #4
Posted 19 March 2014 - 07:39 AM
Yeah pretty much. If you want to load a program with this without changing it's code, you can replace the getfenv with a loadfile. setmetatable should return the function to call the code with the override implemented.
skwerlman #5
Posted 19 March 2014 - 07:44 AM
Well, first there was a typo in your code, but when I fixed that and ran it as wrappedFS.lua I got:
wrappedFS.lua:18: vm error:
java.lang.ArrayIndexOutOfBoundsException
I assume that's unintended?

Wait, nvm, I did something wrong.
Edited on 19 March 2014 - 06:47 AM
CometWolf #6
Posted 19 March 2014 - 07:52 AM
I suppose running it on the global environment might cause some issues, try using it on a loaded program or something.

setmetatable(loadfile"/fileName",
 __index = function(t,k)
    if k == "fs" then
      return overrideFs[k]
    elseif _G[k] and k ~= "_G" then
      return _G[k]
    end
  end
})()
I made a small change that might help aswell.

Edit: typing on my phone is so slow lol, just saw your edit.
Edited on 19 March 2014 - 06:53 AM
oeed #7
Posted 19 March 2014 - 07:54 AM
To anyone else who is trying this and wants to completely 'fool' the program I've found that mimicking the fs API is not sufficient. You'll also need to mimic APIs such as io, paintutils (loadImage), os (loadAPI), loadfile, etc. OneOS does this, if you want to take a look at it check out this file.
Edited on 19 March 2014 - 06:55 AM
skwerlman #8
Posted 19 March 2014 - 08:16 AM
I'll see if I can come up with a way to recursively run CometWolf's routine on every CC API. Then I'll never have to update unless they add a new hardcoded API.
oeed #9
Posted 19 March 2014 - 08:22 AM
I'll see if I can come up with a way to recursively run CometWolf's routine on every CC API. Then I'll never have to update unless they add a new hardcoded API.

Yes, I'd be interested to see if you can, that will prevent the need to manually update OneOS every time APIs are changed or added.
theoriginalbit #10
Posted 19 March 2014 - 09:14 AM
I'll see if I can come up with a way to recursively run CometWolf's routine on every CC API. Then I'll never have to update unless they add a new hardcoded API.
Yes, I'd be interested to see if you can, that will prevent the need to manually update OneOS every time APIs are changed or added.
why not just do it based on the list taken from fs.list(/rom/apis/) if you change the pointer to the FS API in all those to point to the new FS API then you'd be good.
CometWolf #11
Posted 19 March 2014 - 09:19 AM
This would change fs globally, but it would do what you want.

for k,v in pairs(_G) do
  if type(v) == "table" then
    setmetatable(v,
      {
        __index = function(t2,k2)
          if k2 == "fs" then
            return overrideFs[k2]
          end
          return _G[k2]
        end
      }
    )
  end
end
I can take a shot at sandboxing this when im not on my phone :P/>
oeed #12
Posted 19 March 2014 - 09:32 AM
I'll see if I can come up with a way to recursively run CometWolf's routine on every CC API. Then I'll never have to update unless they add a new hardcoded API.
Yes, I'd be interested to see if you can, that will prevent the need to manually update OneOS every time APIs are changed or added.
why not just do it based on the list taken from fs.list(/rom/apis/) if you change the pointer to the FS API in all those to point to the new FS API then you'd be good.
Would it though? I found that the API's FS wouldn't change.
skwerlman #13
Posted 19 March 2014 - 09:35 AM
I'll see if I can come up with a way to recursively run CometWolf's routine on every CC API. Then I'll never have to update unless they add a new hardcoded API.
Yes, I'd be interested to see if you can, that will prevent the need to manually update OneOS every time APIs are changed or added.
why not just do it based on the list taken from fs.list(/rom/apis/) if you change the pointer to the FS API in all those to point to the new FS API then you'd be good.
Would it though? I found that the API's FS wouldn't change.

originalbit came up with an os.loadAPI replacement that would let you pass references of APIs to the loaded API. I use it in LuaGRUB's startup file.

EDIT: I got this far:

local newRoot = "/t/"
local apiDir = '/rom/apis/'
local tAPI = fs.list(apiDir)
local oldFS = fs

for n,e in ipairs(tAPI) do
  if not oldFS.isDir(apiDir..e) then
	print('Unloading '..e)
	os.unloadAPI(apiDir..e)
  end
end

function loadAPI(path)
  local name = string.match(oldFS.getName(path), '(%a+)%.?.-')
  local env = setmetatable({fs = overrideFs, os = overrideOs}, { __index = _G }) -- passes a ref of the shell api to the loaded api
  local func, err = loadfile(path)
  if not func then
	return false, printError(err)
  end
  setfenv(func, env)
  func()
  local api = {}
  for k,v in pairs(env) do
	api[k] = v
  end
  _G[name] = api
  return true
end

local overrideFs= {}
for k,v in ipairs(_G.fs) do
  overrideFs[k] = function(...)
	local args = {...}
	for i=1,#args do
	  if not (k == "open" and i == 2) then
		args[i] = newRoot..args[i]
	  end
	end
	return v(unpack(args))
  end
end

local overrideOs= {}
for k,v in ipairs(_G.os) do
  overrideOs[k] = function(...)
	local args = {...}
	for i=1,#args do
	  if k == "loadAPI" or k == "unloadAPI" then
		args[i] = newRoot..args[i]
	  end
	end
	return v(unpack(args))
  end
end

setmetatable(getfenv(0), {
__index = function(t,k)
	if k ~= "_G" then
	  if k == "fs" then
		return overrideFs[k]
	  elseif k == "os" then
		return overrideOs[k]
	  elseif _G[k] then
		return _G[k]
	  end
	end
  end
})

for n,e in ipairs(tAPI) do
  if not oldFS.isDir(apiDir..e) then
	print('Loading '..e)
	loadAPI(apiDir..e)
  end
end

shell.run('ls') -- in here to make sure shell.isDir() functions properly
The issue now is that every time I run it, I get a vm error at line 64 'java.lang.ArrayIndexOutOfBoundsException' when loading the colors API.
If I could fix that, I think we'd be overriding every API that uses the filesystem. (And then some)
Edited on 19 March 2014 - 10:12 AM
apemanzilla #14
Posted 19 March 2014 - 11:28 AM
Don't forget, if they pass the path ".." They're back in the root
skwerlman #15
Posted 19 March 2014 - 11:32 AM
Don't forget, if they pass the path ".." They're back in the root
Good point. But I might leave that up to OS authors. They may want a root mode, and it's not really my place to restrict them. I may find a way to require a password, though.
apemanzilla #16
Posted 19 March 2014 - 11:58 AM
Don't forget, if they pass the path ".." They're back in the root
Good point. But I might leave that up to OS authors. They may want a root mode, and it's not really my place to restrict them. I may find a way to require a password, though.
Tip: GitHub repo apemanzilla/ApeOs, then navigate to /system/apis/safe_FS.lua

Should give you a pretty well sandboxed fs API. (It also include io.open :P/>)
Edited on 19 March 2014 - 10:59 AM
skwerlman #17
Posted 19 March 2014 - 12:57 PM
Don't forget, if they pass the path ".." They're back in the root
Good point. But I might leave that up to OS authors. They may want a root mode, and it's not really my place to restrict them. I may find a way to require a password, though.
Tip: GitHub repo apemanzilla/ApeOs, then navigate to /system/apis/safe_FS.lua

Should give you a pretty well sandboxed fs API. (It also include io.open :P/>)
I looked at how it's implemented, but it doesn't look like it overrides fs. Do you do that elsewhere?
theoriginalbit #18
Posted 19 March 2014 - 01:06 PM
Don't forget, if they pass the path ".." They're back in the root
Good point. But I might leave that up to OS authors. They may want a root mode, and it's not really my place to restrict them. I may find a way to require a password, though.

local path = root..shell.resolve(filePath)
filePath can contain as many .. as they want, or any forwards slashes or anything, but they're not getting anywhere with it.
the best thing about shell.resolve is that the file does not need to exist for the file path to be sanitised :)/>
skwerlman #19
Posted 19 March 2014 - 01:14 PM
Don't forget, if they pass the path ".." They're back in the root
Good point. But I might leave that up to OS authors. They may want a root mode, and it's not really my place to restrict them. I may find a way to require a password, though.

local path = root..shell.resolve(filePath)
filePath can contain as many .. as they want, or any forwards slashes or anything, but they're not getting anywhere with it.
the best thing about shell.resolve is that the file does not need to exist for the file path to be sanitised :)/>
I tested his hypothesis, and this worked:

> test2
> cd ..
..> ls
rom
test test2
..>
theoriginalbit #20
Posted 19 March 2014 - 01:40 PM
I tested his hypothesis, and this worked:

> test2
> cd ..
..> ls
rom
test test2
..>
what?
skwerlman #21
Posted 19 March 2014 - 01:58 PM
I tested his hypothesis, and this worked:

> test2
> cd ..
..> ls
rom
test test2
..>
what?
I was able to cd into '..', or root, in this case.
There was nothing preventing me from doing so.
theoriginalbit #22
Posted 19 March 2014 - 02:06 PM
I was able to cd into '..', or root, in this case.
There was nothing preventing me from doing so.
after having implemented the code that I suggested?
apemanzilla #23
Posted 19 March 2014 - 03:10 PM
Why don't you just use fs.combine(root,fs.combine("",path))? Yes, I know it looks ugly but it should work fine.
skwerlman #24
Posted 19 March 2014 - 07:30 PM
I was able to cd into '..', or root, in this case.
There was nothing preventing me from doing so.
after having implemented the code that I suggested?
Yeah, since I'm on Linux, '..' is actually a symlink to the parent directory. On windows however, it's a special case handled by cd, so I think your code would work there, but since it's an actual directory on my fs, the only way to sanitize it correctly would be to check for and replace absolute paths equal to '/..'
theoriginalbit #25
Posted 19 March 2014 - 10:43 PM
Yeah, since I'm on Linux, '..' is actually a symlink to the parent directory. On windows however, it's a special case handled by cd, so I think your code would work there, but since it's an actual directory on my fs, the only way to sanitize it correctly would be to check for and replace absolute paths equal to '/..'
I'm on OSX I know what '..' is and its handled the same way as it is in Linux ('cause both are Unix). I performed tests and it worked perfectly fine for me. could I possibly get a copy of the code you used to test?
skwerlman #26
Posted 20 March 2014 - 12:39 AM
Yeah, since I'm on Linux, '..' is actually a symlink to the parent directory. On windows however, it's a special case handled by cd, so I think your code would work there, but since it's an actual directory on my fs, the only way to sanitize it correctly would be to check for and replace absolute paths equal to '/..'
I'm on OSX I know what '..' is and its handled the same way as it is in Linux ('cause both are Unix). I performed tests and it worked perfectly fine for me. could I possibly get a copy of the code you used to test?
Unfortunately, I've been overhauling my code, and I don't think the version that gave me that result exists.
apemanzilla #27
Posted 20 March 2014 - 03:32 AM
Yeah, since I'm on Linux, '..' is actually a symlink to the parent directory. On windows however, it's a special case handled by cd, so I think your code would work there, but since it's an actual directory on my fs, the only way to sanitize it correctly would be to check for and replace absolute paths equal to '/..'
I'm on OSX I know what '..' is and its handled the same way as it is in Linux ('cause both are Unix). I performed tests and it worked perfectly fine for me. could I possibly get a copy of the code you used to test?
Unfortunately, I've been overhauling my code, and I don't think the version that gave me that result exists.
*Facepalm*