Repository: ros/ros_comm

Issue:
Inconsistent node name validation between roscpp and rospy
This works:

```
$ rosrun any_pkg any_rospy_node __name:=foo-bar
```

This does not:

```
$ rosrun any_pkg any_roscpp_node __name:=foo-bar
terminate called after throwing an instance of 'ros::InvalidNameException'
  what():  Character [-] at element [3] is not valid in Graph Resource Name [foo-bar].  Valid characters are a-z, A-Z, 0-9, / and _.
Aborted (core dumped)
```



Discussion:
It looks like that `roscpp` validates the name before using it. `rospy` might simply not perform any validation. Have you tried completely arbitrary names with Python?

The first thing I noticed is that an invalid name in rospy is only a warning when passed to `init_node()`:

https://github.com/ros/ros_comm/blob/indigo-devel/clients/rospy/src/rospy/client.py#L261-L266

Next, a name provided from the command line doesn't even get a warning because it is pulled in after that check:

https://github.com/ros/ros_comm/blob/indigo-devel/clients/rospy/src/rospy/client.py#L261-L266

So, anything that makes it through either implementation of `resolve_name()` (different ones are used for arg and cmdline, and neither validate) is accepted in rospy.  Looks like the validators in `rospy.names` are unfinished too.  The validator in `rosgraph.names` might be appropriate for a node name:

https://github.com/ros/ros_comm/blob/indigo-devel/tools/rosgraph/src/rosgraph/names.py#L235-L244

The pattern is `^[A-Za-z][\w_]*$` (which seems like it could be simplified since `_` is in the set of `\w` according to [the docs](https://docs.python.org/2/library/re.html#regular-expression-syntax)).

roscpp init validation follows the same rules:

https://github.com/ros/ros_comm/blob/indigo-devel/clients/roscpp/src/libros/this_node.cpp#L131-L140

which calls `names::resolve()` which calls:

https://github.com/ros/ros_comm/blob/indigo-devel/clients/roscpp/src/libros/names.cpp#L56-L94

As you have pointed out the validation behavior can clearly be improved / unified for all code paths and languages.

For already released ROS distro we can only add warnings for previously accepted names. If we would suddenly reject them that can cause regressions in existing code (since it might not follow the convention). Therefore I would suggest to only introduce stricter checks resulting warnings for now and in the next ROS release (aka k-turtle) change some of the warnings into errors.

It would be great if someone could work on a PR for this since I might not find the time to work on this change.

Are acceptable node names defined in a REP or some other client standards doc?  Should the master reject bad names in the next distro?

It's sort of a stretch, but there's a vulnerability here.

When shell escapes are allowed in any node identifier, a script can be circumvented to expose the value of a private environment variable.

For example:
- Start a rospy node with `__name:='$HOME'`
- Run `rosnode list` and see that `/$HOME` is in the list.
- Run `tcpdump -X dst port 11311` to see communications with the master.
- Run `rosnode info "$HOME"` (as an ignorant bash script iterating over `rosnode list` might do) and see that the value of `$HOME` is in the captured data.

roscpp's validation ensures that the name can be used as an identifier in practically any language (length aside, maybe) and shell escapes are impossible.

Naming rules for graph resources are defined here: http://wiki.ros.org/Names


Resolving patch:
diff --git a/clients/rospy/src/rospy/client.py b/clients/rospy/src/rospy/client.py
--- a/clients/rospy/src/rospy/client.py
+++ b/clients/rospy/src/rospy/client.py
@@ -261,10 +261,7 @@ def init_node(name, argv=None, anonymous=False, log_level=None, disable_rostime=
     # this test can be eliminated once we change from warning to error in the next check
     if rosgraph.names.SEP in name:
         raise ValueError("namespaces are not allowed in node names")
-    if not rosgraph.names.is_legal_base_name(name):
-        import warnings
-        warnings.warn("'%s' is not a legal ROS base name. This may cause problems with other ROS tools"%name, stacklevel=2)
-    
+
     global _init_node_args
 
     # #972: allow duplicate init_node args if calls are identical
@@ -289,8 +286,7 @@ def init_node(name, argv=None, anonymous=False, log_level=None, disable_rostime=
     # check for name override
     mappings = rospy.names.get_mappings()
     if '__name' in mappings:
-        # use rosgraph version of resolve_name to avoid remapping
-        name = rosgraph.names.resolve_name(mappings['__name'], rospy.core.get_caller_id())
+        name = mappings['__name']
         if anonymous:
             logdebug("[%s] WARNING: due to __name setting, anonymous setting is being changed to false"%name)
             anonymous = False
@@ -300,7 +296,13 @@ def init_node(name, argv=None, anonymous=False, log_level=None, disable_rostime=
         # hostname as that is not guaranteed to be a legal ROS name
         name = "%s_%s_%s"%(name, os.getpid(), int(time.time()*1000))
 
-    resolved_node_name = rospy.names.resolve_name(name)
+    # check for legal base name once all changes have been made to the name
+    if not rosgraph.names.is_legal_base_name(name):
+        import warnings
+        warnings.warn("'%s' is not a legal ROS base name. This may cause problems with other ROS tools."%name, stacklevel=2)
+
+    # use rosgraph version of resolve_name to avoid remapping
+    resolved_node_name = rosgraph.names.resolve_name(name, rospy.core.get_caller_id())
     rospy.core.configure_logging(resolved_node_name)
     # #1810
     rospy.names.initialize_mappings(resolved_node_name)
diff --git a/tools/rosgraph/src/rosgraph/names.py b/tools/rosgraph/src/rosgraph/names.py
--- a/tools/rosgraph/src/rosgraph/names.py
+++ b/tools/rosgraph/src/rosgraph/names.py
@@ -212,7 +212,7 @@ def load_mappings(argv):
 import re
 
 #~,/, or ascii char followed by (alphanumeric, _, /)
-NAME_LEGAL_CHARS_P = re.compile('^[\~\/A-Za-z][\w_\/]*$') 
+NAME_LEGAL_CHARS_P = re.compile('^[\~\/A-Za-z][\w\/]*$')
 def is_legal_name(name):
     """
     Check if name is a legal ROS name for graph resources
@@ -232,7 +232,7 @@ def is_legal_name(name):
     m = NAME_LEGAL_CHARS_P.match(name)
     return m is not None and m.group(0) == name and not '//' in name
     
-BASE_NAME_LEGAL_CHARS_P = re.compile('^[A-Za-z][\w_]*$') #ascii char followed by (alphanumeric, _)
+BASE_NAME_LEGAL_CHARS_P = re.compile('^[A-Za-z][\w]*$') #ascii char followed by (alphanumeric, _)
 def is_legal_base_name(name):
     """
     Validates that name is a legal base name for a graph resource. A base name has


Test patch: