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

[SOLVED] File protection issue in new O

Started by ProjectB, 17 July 2015 - 03:19 AM
ProjectB #1
Posted 17 July 2015 - 05:19 AM
With my operating system O2.2 I am trying to add a way to secure the core system files, but I am running into an issue. The file system blocks access to deleting or modifying most files in systemO and with startup. As soon as you set your working directory into a file the protection no longer works. If you want to plunge into O for the code you can get it at "systemO/launchpad".

How the file protection works.

The file protection runs a shell.resolve onto the filepath, then checks the result. If shell.resolve(filePath) == "startup" it does not allow it, or if the first 7 letters are systemO.

Does anyone have an idea why it would do that?
SpoileroldShellResolve is a backup of shell.resolve

function fs.open(filePath, changeType)
local allowed = 1
if oldShellResolve(filePath) == "startup" then
  allowed = 0
elseif string.sub(oldShellResolve(filePath), 1, 7) == "systemO" then
  allowed = 0
end
if changeType == "r" then
  allowed = 1
end
for i=1,#whitelistedFiles do
  if oldShellResolve(filePath) == whitelistedFiles[i] then
   allowed = 1
  end
end
if allowed == 1 then
  return oldfsopen(filePath, changeType)
else
  error("Access Denied - To modify system files enable dev mode")
end
end
function fs.delete(filePath)
local allowed = 1
if oldShellResolve(filePath) == "startup" then
  allowed = 0
elseif string.sub(oldShellResolve(filePath), 1, 7) == "systemO" then
  allowed = 0
end
for i=1,#whitelistedFiles do
  if oldShellResolve(filePath) == whitelistedFiles[i] then
   allowed = 1
  end
end
if allowed == 1 then
  olddelete(filePath)
end
end
function fs.move(filePath, filePath2)
local allowed = 1
if oldShellResolve(filePath) == "startup" then
  allowed = 0
elseif oldShellResolve(filePath2) == "startup" then
  allowed = 0
end
if string.sub(oldShellResolve(filePath), 1, 7) == "systemO" then
  allowed = 0
elseif string.sub(oldShellResolve(filePath2), 1, 7) == "systemO" then
  allowed = 0
end
if allowed == 1 then
  oldfsmove(filePath, filePath2)
end
end
function fs.copy(filePath, filePath2)
local allowed = 1
if oldShellResolve(filePath2) == "startup" then
  allowed = 0
elseif string.sub(oldShellResolve(filePath2), 1, 7) == "systemO" then
  allowed = 0
end
if allowed == 1 then
  oldfscopy(filePath, filePath2)
end
end
function fs.isReadOnly(filePath)
local allowed = 1
if oldShellResolve(filePath) == "startup" then
  allowed = 0
elseif string.sub(oldShellResolve(filePath), 1, 7) == "systemO" then
  allowed = 0
elseif string.sub(oldShellResolve(filePath), 1, 3) == "rom" then
  allowed = 0
end
if allowed == 1 then
  return false
else
  return true
end
end

You can get to O2.2 by running

pastebin run VSNTU31v
Then after run the "stream" program, use your arrow keys to select alpha, following that run the "update" program.
Edited on 17 July 2015 - 03:58 PM
cyanisaac #2
Posted 17 July 2015 - 05:27 AM
Hahaha wow, I don't know. This is an advanced issue, but maybe run some debug stuff to print out values and stuff that will help you track down the issue? Try looking at shell.resolve.
Grim Reaper #3
Posted 17 July 2015 - 08:15 AM
The file system blocks access to deleting or modifying most files in systemO and with startup. Though this appears to work well across the operating system, as soon as you enter the rom folder it seems to ignore the protection.

When you say "enter the rom folder," do you mean that if you set the shell's current directory to anywhere in "rom," that you can start editing/deleting files? Or do you mean that if you append the full path of a program residing in "rom," you can delete files within "systemO?"

In either case, I suggest you take a look at the code you're using to check for valid and invalid paths. Also, if you could, might you be able to explain to us, in high level language, how you went about protecting the system? If your theory isn't flawed, then I'd like to have a look at the code you've implemented to protect the system as I can't seem to find it in your "launchpad" program, although this could just be me not looking in the right place.

The simplest way to secure the file system, if you haven't done this already, is to modify the "fs" API to which all programs will reference. If you need an example of this, I, or many of the other members, can write you one.
Edited on 17 July 2015 - 06:16 AM
ProjectB #4
Posted 17 July 2015 - 08:36 AM
The file system blocks access to deleting or modifying most files in systemO and with startup. Though this appears to work well across the operating system, as soon as you enter the rom folder it seems to ignore the protection.

When you say "enter the rom folder," do you mean that if you set the shell's current directory to anywhere in "rom," that you can start editing/deleting files? Or do you mean that if you append the full path of a program residing in "rom," you can delete files within "systemO?"

In either case, I suggest you take a look at the code you're using to check for valid and invalid paths. Also, if you could, might you be able to explain to us, in high level language, how you went about protecting the system? If your theory isn't flawed, then I'd like to have a look at the code you've implemented to protect the system as I can't seem to find it in your "launchpad" program, although this could just be me not looking in the right place.

The simplest way to secure the file system, if you haven't done this already, is to modify the "fs" API to which all programs will reference. If you need an example of this, I, or many of the other members, can write you one.
I have updated the OP, let me kow if you have more questions.
Grim Reaper #5
Posted 17 July 2015 - 08:58 AM
I have updated the OP, let me kow if you have more questions.

Can you please provide an example scenario, with as many details as possible, in which your system fails?

Also, you've re-written an awful lot of code that could be generalized with nice little loop :)/>
Spoiler

local oldFs = {}

-- Copy all current fs functions.
for name, func in pairs(_G.fs) do
	oldFs[name] = func
end



local function isPathLegal(path)
	-- whiteListedFiles[path] = true (if the path is whitelisted)
	return path and ( (path ~= "startup" and path:sub(1, 7) ~= "systemO") or whitelistedFiles[path] )
end

-- Overwrite the fs API.
for name, func in pairs(oldFs) do
	-- DO NOT OVERRIDE fs.combine!
	if name ~= "combine" then
		_G.fs[name] = function(path, ...)
			local path = oldShellResolve(path)

			if not isPathLegal(path) then
				error("Access Denied -- To modify system files, enable 'dev' mode.")
			end

			-- Some special stuff for fs.move and fs.copy:
			if name == "move" or name == "copy" then
				local destination = ({ ... })[1]

				if not isPathLegal(destination) then
					error("Access Denied -- To modify system files, enable 'dev' mode.")
				end
			end

			-- Not an illegal path -> Proceed normally.
			return oldFs[name](path, ...)
		end
	end
end

-- Debug purposes only: Restores the old fs API.
_G.fs.restore = function()
	for name, func in pairs(oldFs) do
		_G.fs[name] = func
	end
end
Edited on 17 July 2015 - 07:01 AM
ProjectB #6
Posted 17 July 2015 - 09:42 AM
Ok, a specific example of when it fails is, supposed you cd into systemO, running "edit /startup" will ignore the file protection and allow you to modify the file.

After some investigation shell.resolve() is returning something weird.

As you can see the second printed result shows that shell.resolve() isn't working the way I intended it to.


I have updated the OP, let me kow if you have more questions.

Can you please provide an example scenario, with as many details as possible, in which your system fails?

Also, you've re-written an awful lot of code that could be generalized with nice little loop :)/>
Spoiler

local oldFs = {}

-- Copy all current fs functions.
for name, func in pairs(_G.fs) do
	oldFs[name] = func
end



local function isPathLegal(path)
	-- whiteListedFiles[path] = true (if the path is whitelisted)
	return path and ( (path ~= "startup" and path:sub(1, 7) ~= "systemO") or whitelistedFiles[path] )
end

-- Overwrite the fs API.
for name, func in pairs(oldFs) do
	-- DO NOT OVERRIDE fs.combine!
	if name ~= "combine" then
		_G.fs[name] = function(path, ...)
			local path = oldShellResolve(path)

			if not isPathLegal(path) then
				error("Access Denied -- To modify system files, enable 'dev' mode.")
			end

			-- Some special stuff for fs.move and fs.copy:
			if name == "move" or name == "copy" then
				local destination = ({ ... })[1]

				if not isPathLegal(destination) then
					error("Access Denied -- To modify system files, enable 'dev' mode.")
				end
			end

			-- Not an illegal path -> Proceed normally.
			return oldFs[name](path, ...)
		end
	end
end

-- Debug purposes only: Restores the old fs API.
_G.fs.restore = function()
	for name, func in pairs(oldFs) do
		_G.fs[name] = func
	end
end
Thanks, I will look into potentially putting this into O.
Grim Reaper #7
Posted 17 July 2015 - 04:53 PM
In the code you've provided, you reference an

oldShellResolve
, suggesting that you've edited shell.resolve. Would you mind providing the modified code for that, please? Sorry for asking for so much information, but it's very, very helpful for diagnosing larger scale problems :)/>
ProjectB #8
Posted 17 July 2015 - 05:05 PM
oldShellResolve = shell.resolve

I have a backup of it in case a program overwrites that function.
Lignum #9
Posted 17 July 2015 - 05:38 PM
The problem is that shell.resolve isn't fit for the job here. shell.resolve turns a relative path into an absolute path. In root, "startup" would be resolved to "startup", but in a directory called "dir", it would become "dir/startup". Because the fs lib accepts absolute paths, it's safe to assume that the given path is in fact absolute already…

Though a simple string comparison won't quite suffice here. If you pass "/startup" (note the slash) to the fs functions, you'd be able to access the file anyway. You'd need a way to normalise the file path in order to compare it properly. Luckily, I've already written such a function once, which you can use however you like:

fs.normalise = function(path)
		if path == nil then error("path can't be nil", 2) end
		local fullPath = path
		if fullPath ~= nil then
			local continue = true
			if fullPath == "" then fullPath = "/"; continue = false end
			if fullPath == "/" or fullPath == "\\" then fullPath = "/"; continue = false end
			if continue then
				fullPath = fullPath:gsub("\\", "/")
				if fullPath:sub(1, 1) ~= "/" then
					fullPath = "/" .. fullPath
				end
				if fullPath:sub(#fullPath, #fullPath) == "/" then
					fullPath = fullPath:sub(1, #fullPath - 1)
				end
			end
		end
		return fullPath
	end
	fs.normalize = fs.normalise

It's a bit quirky since I've taken it straight from my OS and adjusted it for a general case in the forum editor… but it should work (or at least be easy to correct). It normalises all file paths to this format: "/a/b/c"
So to block system files from being modified, you would do this (pseudocode):

local filePath = ...
local allowed = true
for _,v in pairs({ "/startup", "/aFile", "/anotherFile" }) do
   if fs.normalise(filePath) == v do
	  allowed = false
	  break
   end
end

if not allowed then
   ...
else
   ...
end
ProjectB #10
Posted 17 July 2015 - 05:53 PM
The problem is that shell.resolve isn't fit for the job here. shell.resolve turns a relative path into an absolute path. In root, "startup" would be resolved to "startup", but in a directory called "dir", it would become "dir/startup". Because the fs lib accepts absolute paths, it's safe to assume that the given path is in fact absolute already…

Though a simple string comparison won't quite suffice here. If you pass "/startup" (note the slash) to the fs functions, you'd be able to access the file anyway. You'd need a way to normalise the file path in order to compare it properly. Luckily, I've already written such a function once, which you can use however you like:

fs.normalise = function(path)
		if path == nil then error("path can't be nil", 2) end
		local fullPath = path
		if fullPath ~= nil then
			local continue = true
			if fullPath == "" then fullPath = "/"; continue = false end
			if fullPath == "/" or fullPath == "\\" then fullPath = "/"; continue = false end
			if continue then
				fullPath = fullPath:gsub("\\", "/")
				if fullPath:sub(1, 1) ~= "/" then
					fullPath = "/" .. fullPath
				end
				if fullPath:sub(#fullPath, #fullPath) == "/" then
					fullPath = fullPath:sub(1, #fullPath - 1)
				end
			end
		end
		return fullPath
	end
	fs.normalize = fs.normalise

It's a bit quirky since I've taken it straight from my OS and adjusted it for a general case in the forum editor… but it should work (or at least be easy to correct). It normalises all file paths to this format: "/a/b/c"
So to block system files from being modified, you would do this (pseudocode):

local filePath = ...
local allowed = true
for _,v in pairs({ "/startup", "/aFile", "/anotherFile" }) do
   if fs.normalise(filePath) == v do
	  allowed = false
	  break
   end
end

if not allowed then
   ...
else
   ...
end
Thanks! When I tried that it worked great!