noblemaster
|
 |
«
Posted
2009-10-19 21:16:40 » |
|
I am thinking about adding AI scripting functionality to my game ( http://www.ageofconquest.com). The goal is to let players create their own AI for the game. The game is turn-based, so the AI script will be called every time a new game turn is calculated. AI Input: - a game object AI Output: - a list of actions to execute on the game Constraints:- Required: AI Script can be executed in Java ( platform independent) - Required: prevent malicious code (e.g. prevent access to file system etc.) - Desired: limit AI functionality to just reading the game object and creating actions - Desired: AI editor with syntax coloring (what's good?) What's the best strategy to create AI scripting for players? Use an existing language? Which one? Or roll my own? Let me know if my question is not clear. Thanks 
|
|
|
|
Riven
|
 |
«
Reply #1 - Posted
2009-10-19 22:00:54 » |
|
Probably the easiest (for you) is to launch a separate sandboxed JVM and kill it after a second, if it hasn't terminated yet. You simply serialize some of your game objects, and load them in 'their' JVM (as read-only as it gets, no matter what they write/modify, it won't alter your real game JVM).
It might sound stupid, but it's like 30 minutes to implement, and that's it! Compare that to anything else.
|
Hi, appreciate more people! Σ ♥ = ¾ Learn how to award medals... and work your way up the social rankings!
|
|
|
noblemaster
|
 |
«
Reply #2 - Posted
2009-10-19 22:57:45 » |
|
How about launching the code in a separate thread instead of a sandboxed JVM? I would kill the thread if it doesn't finish after a second (like you proposed for the JVM). I can pass in a copy of the game object. It wouldn't matter if the AI would modify the game object copy. Now, how can a prevent the AI code to access the file system/doing malicious stuff? If I load the AI script via a modified ClassLoader, would I be able to prevent the bad from happening? How about the following for a start  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| ...
String code = ...;
Game gameCopy = game.copy();
Thread scriptRunner = new Thread() { public void run() { Script script = ScriptClassLoader.compileAndLoad(code);
ActionList actions = script.createActions(gameCopy); database.store(actions); } } scriptRunner.start();
Thread.sleep(1000); if (scriptRunner.isActive()) { scriptRunner.interrupt(); } ... |
|
|
|
|
Games published by our own members! Check 'em out!
|
|
|
Riven
|
 |
«
Reply #4 - Posted
2009-10-20 06:08:21 » |
|
 I didn't allow any capticals in the sourcecode (also checking unicodes -- \uHEXX) and I stop()ed the Thread after 100ms. I remember Abuse came up with some obscure class in the JRE that started with a lowercase character, that did something really bad, and I limited the max source length. So... not really a useful strategy to you, unfortunately. FYI: scriptRunner.interrupt(); does nothing. you really must use stop() which will not properly release mutexes.
|
Hi, appreciate more people! Σ ♥ = ¾ Learn how to award medals... and work your way up the social rankings!
|
|
|
Nate
|
 |
«
Reply #5 - Posted
2009-10-20 07:32:22 » |
|
|
|
|
|
noblemaster
|
 |
«
Reply #6 - Posted
2009-10-20 09:19:44 » |
|
In regards to security: did you try to use the SecureClassLoader in Java when loading and executing classes? I think it would limit access to system resources for the class???
In regards to Pnuts: it looks cool, but how is it possible to pass in values/objects?
|
|
|
|
Riven
|
 |
«
Reply #7 - Posted
2009-10-20 12:57:57 » |
|
In regards to security: did you try to use the SecureClassLoader in Java when loading and executing classes? I think it would limit access to system resources for the class???
Well, if you look at the classes in java.io.* and java.net.*, you'll see that those permissions are only checked if there is a SecurityManager installed. That SecurityManager will interfere with your game too. Hence the idea of launching an external (sandboxed) JVM.
|
Hi, appreciate more people! Σ ♥ = ¾ Learn how to award medals... and work your way up the social rankings!
|
|
|
DzzD
|
 |
«
Reply #8 - Posted
2009-10-20 17:44:37 » |
|
maybe a stupid idea...dono.... cant you use a custom class loader (force it to the user script) ? and only allow safe classes by checking if it is in your safe classes a list ? dont know hw it is possible but for example your script could only allow getSafeClass("Name") and prevent any Blabla.something() or new Something(), just a search and replace in the user source.
EDIT:
something like :
"Something." => not enabled if Something not in safe list "new "=> not enabled getClass().newInstance() => replaced at compil time by getSafeClassInstance();
|
|
|
|
noblemaster
|
 |
«
Reply #9 - Posted
2009-10-20 20:10:47 » |
|
I dug deep and found the AccessController: http://java.sun.com/j2se/1.5.0/docs/api/java/security/AccessController.htmlHere is how it would look: 1 2 3 4 5 6 7 8 9 10 11
| somemethod() { AccessController.doPrivileged(new PrivilegedAction() { public Object run() { } }, context); ...normal code here... } |
context is AccessControlContext with the access permissions. I think that should do it? The context would be very limited to prevent access to file system etc. Or is my assumption wrong?
|
|
|
|
Games published by our own members! Check 'em out!
|
|
zingbat
|
 |
«
Reply #10 - Posted
2009-10-20 20:17:28 » |
|
What i tested some days ago was to use Janino to dynamically compile scripts and run them with a SecurityManager that only permits using your game classes. you can't hide classes but you can prevent moders from using them and with Janino you can tell it to compile only class bodies and prevent the use of the import instruction.
|
|
|
|
Riven
|
 |
«
Reply #11 - Posted
2009-10-20 20:22:54 » |
|
What i tested some days ago was to use Janino to dynamically compile scripts and run them with a SecurityManager that only permits using your game classes. You can't do that with a SecurityManager. you can't hide classes but you can prevent moders from using them and with Janino you can tell it to compile only class bodies and prevent the use of the import instruction.
You can use fully qualified class names.
|
Hi, appreciate more people! Σ ♥ = ¾ Learn how to award medals... and work your way up the social rankings!
|
|
|
Nate
|
 |
«
Reply #12 - Posted
2009-10-20 21:21:03 » |
|
In regards to Pnuts: it looks cool, but how is it possible to pass in values/objects?
1 2 3 4 5
| Package pkg = new Package(); pkg.set("someVariable".intern(), "any java object"); pkg.setConstant("someConstant".intern(), "any java object"); Context context = new Context(pkg); Pnuts.load(scriptStreamUrlReaderOrString, context); |
You can control whether scripts are interpreted, compiled, cached, etc with context.setImplementation: http://pnuts.org/apidoc/pnuts/lang/PnutsImpl.htmlAlso, context.setVerbose is useful, and I like to call context.setWriter(new PrintWriter(System.out, true)); (the default PrintWriter doesn't autoflush for whatever reason). You can also pass in Pnuts functions in addition to objects: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| pkg.setConstant("moo".intern(), new PnutsFunction () { public boolean defined (int nargs) { return nargs == 1; }
protected Object exec (Object[] args, Context context) { if (!defined(args.length)) undefined(args, context); String someString = (String)args[0]; return null; }
public String toString () { return "function moo(String)"; } }); |
Another way to provide functionality to your scripts is the Pnuts module system. It is powerful and easy to use. http://pnuts.org/1.2.1/snapshot/20070724/doc/lang.html#modulesHere is how you'd use a modified classloader with Pnuts, as kingaschi mentioned: 1 2 3 4 5 6 7 8 9 10
| context.setClassLoader(new ClassLoader() { protected synchronized Class<? > loadClass (String name, boolean resolve) throws ClassNotFoundException { if (name.startsWith("com.example.your.game.")) { Class c = getParent().loadClass(name); if (resolve) resolveClass(c); return c; } throw new ClassNotFoundException("Unable to load class: " + name); } }); |
That seems a pretty secure way to prevent malicious classes from being loaded, but I would definitely hesitate to claim it is bulletproof without a lot more thought.
|
|
|
|
Riven
|
 |
«
Reply #13 - Posted
2009-10-20 22:33:01 » |
|
From your point in your initial post: - Required: prevent malicious code (e.g. prevent access to file system etc.)
So I assume the code will be running on the server, or in remote clients.
Where ever your potentially malicious code is running, would you really think the best solution is to 'plug all security holes you find' ? You'll be surprised how much effort people put into trying to find a vulnerability. In the end it is your game, and it is your responsibility that nobody gets they files corrupted or their creditcard number stolen.
My advice: play it safe and launch 1 sandboxed JVM for each AI player. In that sandboxed JVM you can kill threads each turn (if needed), for each player.
|
Hi, appreciate more people! Σ ♥ = ¾ Learn how to award medals... and work your way up the social rankings!
|
|
|
noblemaster
|
 |
«
Reply #14 - Posted
2009-10-20 23:42:35 » |
|
1 2 3 4 5
| Package pkg = new Package(); pkg.set("someVariable".intern(), "any java object"); pkg.setConstant("someConstant".intern(), "any java object"); Context context = new Context(pkg); Pnuts.load(scriptStreamUrlReaderOrString, context); |
Thanks, that looks like this could work? Let me see if I understand it correctly? 1 2 3 4 5 6 7 8 9 10 11
| Game game = ...
Pnuts script = Pnuts.parse(sourceCode);
Package pkg = new Package(); pkg.set("game".intern(), game); Context context = new Context(pkg); script.run(context);
|
Question: script.run can manipulate the game object I passed in??? I.e. after the script is done, the game object has changed?
|
|
|
|
SimonH
|
 |
«
Reply #15 - Posted
2009-10-20 23:56:34 » |
|
Or roll my own? I did a serious 3 month project on dynamic scripting (ie an agent's script changes depending on its circumstances) and I found that home-brew was best. No compiler security issues, easier to read script (because it's dedicated to the context), and no WTFs from rhino, lua, pnuts &c...
|
People make games and games make people
|
|
|
Nate
|
 |
«
Reply #16 - Posted
2009-10-21 00:22:14 » |
|
script.run can manipulate the game object I passed in??? I.e. after the script is done, the game object has changed?
If your game object has methods that can be called to change the internal state of the object, then yes, the script could have modified it. Either don't provide those methods or make a copy. It is possible to intercept method invocations and other things using a Configuration and context.setConfiguration. This might be used in addition to the classloader. For simpler scripting there is fscript: http://fscript.sourceforge.net/Riven makes a good point. How are you going to be certain you are really secure? If mostly secure is good enough, the classloader is probably fine.
|
|
|
|
noblemaster
|
 |
«
Reply #17 - Posted
2009-10-21 01:34:59 » |
|
If your game object has methods that can be called to change the internal state of the object, then yes, the script could have modified it. Yes, that's what I actually want! Seems like Pnuts is the way to go. So, my assumptions from above how to create and run a script are basically correct? I also checked FScript which looks nice but is probably too limited? Then rolling my own would take lots of time to implement. My goal isn't to create a scripting language but to add one to my game. Using an existing solution is probably the most feasible solution. Once thing I noted about Pnuts is, it doesn't have any updates since 2007? Is there an official forum for it too?
|
|
|
|
Nate
|
 |
«
Reply #18 - Posted
2009-10-21 02:57:10 » |
|
Yeah, the code you outlined should do what you expect. Honestly, you could use any JVM scripting language (Rhino, JRuby, Jython, Groovy, blah). I like Pnuts a lot though! It has nice syntax that doesn't stray too far from Java (compared to the ridiculousness in Groovy). I also like that the Pnuts type system is the Java type system, many languages converted to run on the JVM come with their own type system. Development on Pnuts started in 1997. It is stable and actively maintained. The java.net Pnuts mailing list is the best way to ask questions or discuss bugs. Any bugs found are usually fixed with 24 hours. You might check out this page: http://pnuts.org/articles/pnutsHighlights.html
|
|
|
|
Wrist Radar
Junior Newbie
|
 |
«
Reply #19 - Posted
2010-02-06 12:01:09 » |
|
What's the best strategy to create AI scripting for players? Use an existing language? Which one? Or roll my own? hi! I don't know if someone already told you, but if JavaScript is ok for your purposes you can use Java Scripting technologies: http://java.sun.com/javase/6/docs/technotes/guides/scripting/index.htmlI'm making a game that uses it. code example: 1 2 3 4
| String scriptCode = ... ; ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("JavaScript"); scriptEngine.put("out", System.out); scriptEngine.eval(scriptCode) |
script example: 1
| out.println("Hello, world!"); |
very easy 
|
|
|
|
noblemaster
|
 |
«
Reply #20 - Posted
2010-02-06 22:38:13 » |
|
I am aware of JavaScript for Java. As I am targeting Java 5, scripting is not available there yet. Also, the current version doesn't prevent people from executing malicious code. I am planning to run it on my server, so limiting script execution is quite important! Anyhow, I am going with Pnuts. I got everything to work nicely 
|
|
|
|
Riven
|
 |
«
Reply #21 - Posted
2010-02-06 22:42:35 » |
|
I am aware of JavaScript for Java. As I am targeting Java 5, scripting is not available there yet. Also, the current version doesn't prevent people from executing malicious code. I am planning to run it on my server, so limiting script execution is quite important! Anyhow, I am going with Pnuts. I got everything to work nicely  Rhino is independent of Java 6 scripting APIs, and with a proper ClassShutter implementation, it's as secure as it gets. I bet the players will find JavaScript easier than Pnuts. But heck, I should mind my own business!
|
Hi, appreciate more people! Σ ♥ = ¾ Learn how to award medals... and work your way up the social rankings!
|
|
|
DzzD
|
 |
«
Reply #22 - Posted
2010-02-06 23:04:26 » |
|
I am aware of JavaScript for Java. As I am targeting Java 5, scripting is not available there yet. Also, the current version doesn't prevent people from executing malicious code. I am planning to run it on my server, so limiting script execution is quite important! Anyhow, I am going with Pnuts. I got everything to work nicely  did not kneew about Pnuts, It look really great @Riven : your sig is every days more funny  , what next ? oldness of the user ? credit card number ? 
|
|
|
|
noblemaster
|
 |
«
Reply #23 - Posted
2010-02-07 00:12:53 » |
|
Yeah, Rhino might work!  Anyhow, Pnuts is very close to Java, so I'll stick with it for now. Also, I already implemented & tested Pnuts, so I'll defer other scripting languages to later...
|
|
|
|
|