non-constant strings

Discussion in 'Ruby' started by Dmitry Bilunov, Aug 7, 2007.

  1. 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).
     
    Dmitry Bilunov, Aug 7, 2007
    #1
    1. Advertisements

  2. This will result in a no method error, no?
    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
    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).

    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.
     
    Skye Shaw!@#$, Aug 8, 2007
    #2
    1. Advertisements

  3. It is just an example, assume that you have some safety-checking code
    here.
    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.
    Ruby string has array of chars, length (logical array size) and capacity
    (physical array size, probably larger, that logical) as Java's
    StringBuffer does.
    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?
     
    Dmitry Bilunov, Aug 8, 2007
    #3
  4. Dmitry Bilunov

    Eric Hodel Guest

    Ruby lets you shoot yourself in the foot.

    http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/79333
    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
     
    Eric Hodel, Aug 8, 2007
    #4
  5. Dmitry Bilunov, Aug 8, 2007
    #5
  6. 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.
     
    Dmitry Bilunov, Aug 8, 2007
    #6
  7. Dmitry Bilunov

    Jano Svitok Guest

    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
     
    Jano Svitok, Aug 8, 2007
    #7
  8. Thanks, now it makes sense for me.
     
    Dmitry Bilunov, Aug 8, 2007
    #8
  9. 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
     
    Robert Klemme, Aug 8, 2007
    #9
  10. Dmitry Bilunov

    Kaldrenon Guest

    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*)
     
    Kaldrenon, Aug 8, 2007
    #10
  11. 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.
    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
     
    Robert Klemme, Aug 8, 2007
    #11
  12. Dmitry Bilunov

    dohzya Guest

    Le mercredi 08 août 2007 à 23:04 +0900, Robert Klemme a écrit :
    see by your eyes :
    TypeError: can't modify frozen string
    from (irb):4:in `<<'
    from (irb):4=> "test"

    s is frozen and not changed
     
    dohzya, Aug 8, 2007
    #12
  13. 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
     
    Robert Klemme, Aug 9, 2007
    #13
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.