non-constant strings

D

Dmitry Bilunov

Hello. Why does Ruby have non-constant strings? It seems there is a way
to bypass object encapsulation paradigm and break object integrity. Here
is any example:

class SecureRunner
# This class implements a sudo-like
# runner

def initialize(command)
# Creates an instance. Guaranties, that a command is safe.
if command.safe?
@comamnd = command
else
raise RuntimeError, "Security check failed!"
end
end

def run
# Only safe commands should be run
system(@command)
end
end

# This class seems to be safe
# Here is a way to bypass security check:

command = "some_safe_command"
runner = SecureRunner.new(command)
# a command is safe, so check will be passed

command.replace("evil_command") # BYPASS THE CHECK

runner.run # runs evil_command, that is not safe

The same can be done to fields of instances, which are exported as
read-only (attr_reader). I know there is a way to fix it (using .clone
or .dup), but what is the reason Ruby has non-constant strings, as most
languages (Java, Python) do? Is there a way to disable such behaviour
($SAFE will not help, because internal class methods will not be able to
change instance-variable strings too).
 
S

Skye Shaw!@#$

Hello. Why does Ruby have non-constant strings? It seems there is a way
to bypass object encapsulation paradigm and break object integrity. Here
is any example:

class SecureRunner
# This class implements a sudo-like
# runner

def initialize(command)
# Creates an instance. Guaranties, that a command is safe.
if command.safe?

This will result in a no method error, no?
@comamnd = command
else
raise RuntimeError, "Security check failed!"
end
end

def run
# Only safe commands should be run
system(@command)
end
end

# This class seems to be safe
# Here is a way to bypass security check:

command = "some_safe_command"
runner = SecureRunner.new(command)
# a command is safe, so check will be passed

command.replace("evil_command") # BYPASS THE CHECK
runner.run # runs evil_command, that is not safe

Well, this is not the fault of the language, rather your SecureRunner
class.

def run
# Only safe commands should be run
if @command.tainted?
raise RuntimeError, "Security check failed!"
end
system(@command)
end
The same can be done to fields of instances, which are exported as
read-only (attr_reader).

This is the case any language where arguments are passed by reference.
but what is the reason Ruby has non-constant strings

You mean mutable strings. Ruby does have constant strings:

irb(main):001:0> CONST="assbasscass"
=> "assbasscass"
irb(main):002:0> CONST=123
(irb):2: warning: already initialized constant CONST
=> 123

The String class wraps an array of chars, realloc()'in as necessary
(ruby hackers correct me if I've mistakin).

Consider this in Java:

class SomeClassThatRequiredAlotOfTyping
{
private StringBuffer sb
//....
public void printBuffer() { System.out.println(sb); }
}

StringBuffer sb = new StringBuffer("Dont Chnage!");

SomeClassThatRequiredAlotOfTyping clazz = new
SomeClassThatRequiredAlotOfTyping(sb);
// I'll show that private "read-only" var who's in charge!
sb.delete(0,sb.length());
sb.append("VB6, its ByVal keyword rocked... Not!");
clazz.printBuffer();

Hope that helps.
 
D

Dmitry Bilunov

Skye said:
This will result in a no method error, no?
It is just an example, assume that you have some safety-checking code
here.
Well, this is not the fault of the language, rather your SecureRunner
class.
Yes, it is not fault of Ruby, I am just trying to understand, why do the
strings work in a such way. Most operations can be done with immutable
strings - an operation can return a new string, constructed from method
caller and, optionally, arguments.
This is the case any language where arguments are passed by reference.
You mean mutable strings. Ruby does have constant strings:

irb(main):001:0> CONST="assbasscass"
=> "assbasscass"
irb(main):002:0> CONST=123
(irb):2: warning: already initialized constant CONST
=> 123

The String class wraps an array of chars, realloc()'in as necessary
(ruby hackers correct me if I've mistakin).
Ruby string has array of chars, length (logical array size) and capacity
(physical array size, probably larger, that logical) as Java's
StringBuffer does.
Consider this in Java:

class SomeClassThatRequiredAlotOfTyping
{
private StringBuffer sb
//....
public void printBuffer() { System.out.println(sb); }
}

StringBuffer sb = new StringBuffer("Dont Chnage!");

SomeClassThatRequiredAlotOfTyping clazz = new
SomeClassThatRequiredAlotOfTyping(sb);
// I'll show that private "read-only" var who's in charge!
sb.delete(0,sb.length());
sb.append("VB6, its ByVal keyword rocked... Not!");
clazz.printBuffer();

Hope that helps.

But Java has also String, not just StringBuffer. Is there anything like
String to protect a program against such things? Or some version of
freeze, which freezes a variable (field) to anyone, except instance
methods?
 
E

Eric Hodel

Hello. Why does Ruby have non-constant strings? It seems there is a
way
to bypass object encapsulation paradigm and break object integrity.

Ruby lets you shoot yourself in the foot.

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/79333
Is there a way to disable such behaviour ($SAFE will not help,
because internal class methods will not be able to change instance-
variable strings too).

Nope.

You can search the mailing list archives for further discussion on
the mutability of strings:

http://blade.nagaokaut.ac.jp/ruby/ruby-talk/index.shtml
 
D

Dmitry Bilunov

dohzya said:
Hi,


You want to freeze the object @command, not the class String, don't
you ?
So you don't need to use an Immutable_String class, just add
@command.freeze in your code

It will make impossible changing @command inside instance methods (which
can be guaranteed to be safe). If there is a way to unfreeze a frozen
object, then this method could be called externally and one can gain
write access to an internal instance variable.
 
J

Jano Svitok

It will make impossible changing @command inside instance methods (which
can be guaranteed to be safe). If there is a way to unfreeze a frozen
object, then this method could be called externally and one can gain
write access to an internal instance variable.

Not exactly.

@command is just a pointer to a string, incidentally pointing to same
string as command pointer points to.

So you have three things: 1. the string "safe_command" 2. variable
(=pointer) command, 3. member variable (=pointer) @command.

Now, if you issue @command.freeze, you are not freezing the pointer
@command, but the string it points to, i.e. "safe_command". So if you
do @command.freeze, it is the same as doing "safe_pointer".freeze.

After @command.freeze, you cannot do command.replace - because command
points to a frozen string. However, you can still do command = "abc",
i.e. make command point to another string.

Finally after @command.freeze, you can't do @command.replace either.
But you can do @command = "adsfg" and thus make the pointer point to
another string.

In fact, Hash#[]= dups and freezes String keys, just for the same reason.

Jano
 
D

Dmitry Bilunov

Jano said:
It will make impossible changing @command inside instance methods (which
can be guaranteed to be safe). If there is a way to unfreeze a frozen
object, then this method could be called externally and one can gain
write access to an internal instance variable.

Not exactly.
[cut]

In fact, Hash#[]= dups and freezes String keys, just for the same
reason.

Jano

Thanks, now it makes sense for me.
 
R

Robert Klemme

2007/8/7 said:
Hello. Why does Ruby have non-constant strings? It seems there is a way
to bypass object encapsulation paradigm and break object integrity. Here
is any example:

class SecureRunner
# This class implements a sudo-like
# runner

def initialize(command)
# Creates an instance. Guaranties, that a command is safe.
if command.safe?
@comamnd = command
else
raise RuntimeError, "Security check failed!"
end
end

def run
# Only safe commands should be run
system(@command)
end
end

# This class seems to be safe
# Here is a way to bypass security check:

command = "some_safe_command"
runner = SecureRunner.new(command)
# a command is safe, so check will be passed

command.replace("evil_command") # BYPASS THE CHECK

runner.run # runs evil_command, that is not safe

That is easily fixed:

# Creates an instance. Guaranties, that a command is safe.
def initialize(command)
# side effect free fix:
command = command.dup
# alternative fix: command.freeze
if command.safe?
@comamnd = command
else
raise RuntimeError, "Security check failed!"
end
end

Now you can change the original string in as many ways as you like
without doing any harm. As easy as that.

And I'd like to add it's not the fault of the language if code like
this fails. In fact there are numerous arguments in favor of having
mutable *and* immutable strings vs. having mutable strings only.

Kind regards

robert
 
K

Kaldrenon

Not exactly.

Shortest, closest thing I can think of for unfreezing an object is to
dup it.

a = "test" --> "test"
a.freeze --> 1
a << "test" --> TypeError
a = a.dup --> 1
a << "test" --> "testtest"

(sorry about the non-irb format...default command shell in WinXP
doesn't allow copy/paste. *sigh*)
 
R

Robert Klemme

2007/8/8 said:
Shortest, closest thing I can think of for unfreezing an object is to
dup it.

a = "test" --> "test"
a.freeze --> 1
a << "test" --> TypeError
a = a.dup --> 1
a << "test" --> "testtest"

Just to make it clear: this is not unfreezing - it will just create a
new instance that is not frozen so the original is still safe.
(sorry about the non-irb format...default command shell in WinXP
doesn't allow copy/paste. *sigh*)

You can copy and past from any Windows command line shell. You can
even configure it so you can directly mark with the mouse and copy by
pressing enter.

Kind regards

robert
 
D

dohzya

Le mercredi 08 août 2007 à 23:04 +0900, Robert Klemme a écrit :
2007/8/8, Kaldrenon <[email protected]>:
Just to make it clear: this is not unfreezing - it will just create a
new instance that is not frozen so the original is still safe.

see by your eyes :
TypeError: can't modify frozen string
from (irb):4:in `<<'
from (irb):4=> "test"

s is frozen and not changed
 
R

Robert Klemme

2007/8/8 said:
Le mercredi 08 ao=FBt 2007 =E0 23:04 +0900, Robert Klemme a =E9crit :

see by your eyes :

I am not sure what you are trying to insinuate. Your code just
stresses my point: there is a difference between the imaginary
"a.unfreeze" and "a =3D a.dup" because in the first example there is
just one instance involved while in the second there are two. Any
piece of code that holds on to the original will still have a
reference to the original with the dup approach. Both approaches are
*not* equivalent, which might seem like a subtlety to some - but it is
a crucial point to remember.

Kind regards

robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top