Skip to content

ptr char implicitly converts to cstring, resulting in undefined behavior #13790

@timotheecour

Description

@timotheecour
  • ptr char implicitly converts to cstring, resulting in undefined behavior
  • ditto with ptr array[N, char] and ptr UncheckedArray[N, char]
    this introduces a weird special case in the language (eg $a doesn't compile for other types a: ptr[T] except for T=char) as well as weird bugs and undefined behavior in seemingly safe code

Example 1

when true:
  import std/unittest
  var x: ptr char
  var s: cstring
  doAssert cstring isnot ptr char
  doAssert (ptr char) isnot cstring

  proc fun(a: cstring)=discard
  proc fun2(a: ptr char)=discard
  doAssert not compiles(fun2 cstring"asdf")
  doAssert not compiles(block: x = s)

  # BUG
  check not compiles(fun(x))
  check not compiles(echo(x))
  check not compiles($(x))
  check not compiles(block: s = x)

Current Output

/Users/timothee/git_clone/nim/timn/tests/nim/all/t10433.nim(15, 8): Check failed: not compiles(fun(x))
compiles(fun(x)) was true
/Users/timothee/git_clone/nim/timn/tests/nim/all/t10433.nim(16, 8): Check failed: not compiles(echo(x))
compiles(echo(x)) was true
/Users/timothee/git_clone/nim/timn/tests/nim/all/t10433.nim(17, 8): Check failed: not compiles($(x))
compiles($(x)) was true

Expected Output

tests pass
ie, it should be illegal to do those implicit conversions from ptr char to cstring

Example 2

in case you may think it's a good idea to have implicit conversion from ptr char to cstring, consider this:

when true:
  proc fun(a, b: var openArray[char]) =
    echo a[0].addr # prints abcdef sometimes, or sometimes other garbage
  proc main()=
    var a = ['a','b', 'c']
    var b = ['d','e', 'f']
    fun(a,b)
  main()

you get (different) garbage results when recompiling the same program:

nim c -r $timn_D/tests/nim/all/t10433.nim
abc`UG
nim c -r $timn_D/tests/nim/all/t10433.nim
abc`%

Example 3

in other cases, you'll end up with buffer overflow bugs/attacks, since a ptr char could come from any source (eg ffi calls, malloc / createU, etc) with nothing that should guarantee that it's 0 terminated.

That problem is exacerbated when a ptr char field is nested somewhere deep inside an object and we are calling $ on it

Example 4

likewise, implicit conversion from ptr array[0..2, char] to cstring should be disallowed. It has the same caveats, eg:

when true:
  var vals = @[['a','b','c'], ['d','e','f']]
  echo vals[0]
  echo vals[0].addr # should not compile (unless `$` has an overload for ptr)
  var z: cstring = vals[0].addr # should not compile

prints

['a', 'b', 'c']
abcdef # error prone!

and ditto for ptr UncheckedArray, eg:

when true:
  var vals1 = @[['a','b','c'], ['d','e','f']]
  echo cast[ptr UncheckedArray[char]](vals1[0].addr) # prints

Possible Solution

  • prevent implicit conversion (sigmatch) from X to cstring where X is not cstring (eg ptr char, ptr[array[N, char] etc)
  • add an easy way to convert explicitly, eg:
template toCstring*(a: ptr char): cstring = cast[cstring](a)
  • if the worry is about backward compatibility, then make it an error but allow a flag:
    --legacy:implicitPtrCharToCstring (adding to existing list --legacy:allowSemcheckedAstModification|checkUnsignedConversions)

Additional Information

  • latest devel 0a49fe5 1.1.1
  • since at least 0.17 or forever
  • D20200327T225837

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions