diff options
author | Yohann Roussel <yroussel@google.com> | 2015-04-14 10:59:21 +0200 |
---|---|---|
committer | Yohann Roussel <yroussel@google.com> | 2015-04-14 21:45:40 +0200 |
commit | 6909f869f245885370755490be2a6cbf99db5216 (patch) | |
tree | b930339534debb829b9c48bd38ea165d59987acb /jack-launcher/src | |
parent | 97fcd1f2f8d4a6f15dc56e04107817283bc521c4 (diff) | |
download | toolchain_jack-6909f869f245885370755490be2a6cbf99db5216.zip toolchain_jack-6909f869f245885370755490be2a6cbf99db5216.tar.gz toolchain_jack-6909f869f245885370755490be2a6cbf99db5216.tar.bz2 |
A launcher protecting for classpath entries deletion
Allows to run application loaded in a classloader resistant to classpath
entries deletion.
Bug: 20132430
Change-Id: If6f999956b2230651d2ca1aea073848cfd8ff60d
Diffstat (limited to 'jack-launcher/src')
3 files changed, 392 insertions, 0 deletions
diff --git a/jack-launcher/src/com/android/jack/launcher/Main.java b/jack-launcher/src/com/android/jack/launcher/Main.java new file mode 100644 index 0000000..31a09bd --- /dev/null +++ b/jack-launcher/src/com/android/jack/launcher/Main.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.launcher; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import java.util.zip.ZipFile; + +import javax.annotation.Nonnull; + +/** + * Launcher protecting called main class from deletion of its classpath. + */ +public class Main { + + @Nonnull + private static Logger logger = Logger.getLogger(Main.class.getName()); + + private static final int CMD_IDX_CLASSPATH_OPTION = 0; + private static final int CMD_IDX_CLASSPATH_VALUE = 1; + private static final int CMD_IDX_MAIN_CLASS = 2; + private static final int CMD_HANDLED_IDX = 3; + + + public static void main(@Nonnull String[] args) { + if (args.length < CMD_HANDLED_IDX) { + if (args.length < CMD_HANDLED_IDX) { + logUsage(); + abort(); + } + } + + if (!args[CMD_IDX_CLASSPATH_OPTION].equals("-cp")) { + logUsage(); + abort(); + } + + String classpath = args[CMD_IDX_CLASSPATH_VALUE]; + String[] classpathEntries = classpath.split(Pattern.quote(File.pathSeparator)); + + ZipFile[] zips = new ZipFile[classpathEntries.length]; + for (int i = 0; i < zips.length; i++) { + try { + zips[i] = new ZipFile(classpathEntries[i]); + } catch (IOException e) { + logger.log(Level.INFO, "IOEception while trying to open '" + classpathEntries[i] + "'", e); + logger.log(Level.SEVERE, "Failed to open '" + classpathEntries[i] + "'"); + abort(); + } + } + + ClassLoader loader = new ZipLoader(zips); + Class<?> clazz; + try { + clazz = loader.loadClass(args[CMD_IDX_MAIN_CLASS]); + } catch (ClassNotFoundException e) { + logger.log(Level.SEVERE, "Failed to find class '" + args[CMD_IDX_MAIN_CLASS] + "'"); + abort(); + return; + } + Method method; + try { + method = clazz.getMethod("main", args.getClass()); + } catch (SecurityException e) { + throw new AssertionError(); + } catch (NoSuchMethodException e) { + logger.log(Level.SEVERE, "Failed to find main method in class '" + args[CMD_IDX_MAIN_CLASS] + + "'"); + abort(); + return; + } + String[] invokeArgs = new String[args.length - CMD_HANDLED_IDX]; + System.arraycopy(args, CMD_HANDLED_IDX, invokeArgs, 0, args.length - CMD_HANDLED_IDX); + try { + method.invoke(null, (Object) invokeArgs); + } catch (IllegalArgumentException e) { + throw new AssertionError(); + } catch (IllegalAccessException e) { + throw new AssertionError(); + } catch (InvocationTargetException e) { + logger.log(Level.SEVERE, "An error occured during delegate execution", e.getCause()); + abort(); + } + } + + private static void logUsage() { + logger.log(Level.SEVERE, "Usage: -cp <classpath> <main class> ..."); + } + + private static void abort() { + System.exit(1); + } + +} diff --git a/jack-launcher/src/com/android/jack/launcher/ZipLoader.java b/jack-launcher/src/com/android/jack/launcher/ZipLoader.java new file mode 100644 index 0000000..2994823 --- /dev/null +++ b/jack-launcher/src/com/android/jack/launcher/ZipLoader.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.launcher; + +import com.android.jack.launcher.util.BytesStreamSucker; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.util.Enumeration; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +class ZipLoader extends ClassLoader { + + private static final char BANG = '!'; + static { + // ensure class is loaded early, don't wait for first usage + ZipURLConnection.class.getName(); + } + + private static class ZipURLConnection extends URLConnection { + @Nonnull + private final ZipFile zip; + @Nonnull + private final ZipEntry entry; + + ZipURLConnection(@Nonnull URL u, @Nonnull ZipFile zip, @Nonnull ZipEntry entry) { + super(u); + this.zip = zip; + this.entry = entry; + } + + @Override + public void connect() { + // nothing to do + } + + @Override + @Nonnull + public InputStream getInputStream() throws IOException { + return zip.getInputStream(entry); + } + + } + + private static class ZipURLStreamHandler extends URLStreamHandler { + @Nonnull + private final ZipFile zip; + + ZipURLStreamHandler(@Nonnull ZipFile zip) { + this.zip = zip; + } + + @Override + protected URLConnection openConnection(@Nonnull URL u) throws IOException { + ZipEntry entry = zip.getEntry(getZipEntry(u)); + if (entry == null) { + throw new FileNotFoundException(u.toString()); + } + return new ZipURLConnection(u, zip, entry); + } + } + + @Nonnull + private static URL makeURL(@Nonnull ZipFile file, @Nonnull ZipEntry entry, + @Nonnull ZipURLStreamHandler handler) { + try { + assert entry.getName().indexOf(BANG) == -1; + return new URL("launcherzip", "", -1, file.getName() + BANG + entry.getName(), handler); + } catch (MalformedURLException e) { + throw new AssertionError(); + } + } + + @Nonnull + private static String getZipEntry(@Nonnull URL url) { + String file = url.getFile(); + int dashIndex = file.lastIndexOf(BANG); + return file.substring(dashIndex + 1); + } + + @Nonnull + private final ZipFile[] entries; + @Nonnull + private final ZipURLStreamHandler[] handlers; + + + public ZipLoader(@Nonnull ZipFile[] entries) { + this.entries = entries; + handlers = new ZipURLStreamHandler[entries.length]; + for (int i = 0; i < entries.length; i++) { + handlers[i] = new ZipURLStreamHandler(entries[i]); + } + } + + @Nonnull + @Override + protected Class<?> findClass(@Nonnull String name) throws ClassNotFoundException { + + ZipEntry foundEntry = null; + ZipFile foundZip = null; + for (ZipFile zip : entries) { + foundEntry = zip.getEntry(name.replace('.', '/') + ".class"); + if (foundEntry != null) { + foundZip = zip; + break; + } + } + if (foundEntry == null) { + throw new ClassNotFoundException(name); + } + + InputStream in = null; + try { + // FINDBUGS + assert foundZip != null; + in = foundZip.getInputStream(foundEntry); + long size = foundEntry.getSize(); + assert size >= -1 && size <= Integer.MAX_VALUE; + ByteArrayOutputStream out = size == -1 ? new ByteArrayOutputStream() : + new ByteArrayOutputStream((int) size); + new BytesStreamSucker(in, out, /* toBeClose = */ true).suck(); + byte[] classData = out.toByteArray(); + return defineClass(name, classData, 0, classData.length); + } catch (IOException e) { + throw new ClassNotFoundException(name); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + @CheckForNull + @Override + public InputStream getResourceAsStream(@Nonnull String name) { + InputStream found = getParent().getResourceAsStream(name); + if (found != null) { + return found; + } else { + for (ZipFile zip : entries) { + ZipEntry foundEntry = zip.getEntry(name); + if (foundEntry != null) { + try { + return zip.getInputStream(foundEntry); + } catch (IOException e) { + AssertionError error = new AssertionError(); + error.initCause(e); + throw error; + } + } + } + return null; + } + } + + @CheckForNull + @Override + protected URL findResource(@Nonnull String name) { + for (int i = 0; i < entries.length; i++) { + ZipFile zip = entries[i]; + ZipEntry foundEntry = zip.getEntry(name); + if (foundEntry != null) { + return makeURL(zip, foundEntry, handlers[i]); + } + } + return null; + } + + @Nonnull + @Override + protected Enumeration<URL> findResources(@Nonnull String name) { + Vector<URL> vector = new Vector<URL>(); + for (int i = 0; i < entries.length; i++) { + ZipFile zip = entries[i]; + ZipEntry foundEntry = zip.getEntry(name); + if (foundEntry != null) { + vector.add(makeURL(zip, foundEntry, handlers[i])); + } + } + return vector.elements(); + } +} diff --git a/jack-launcher/src/com/android/jack/launcher/util/BytesStreamSucker.java b/jack-launcher/src/com/android/jack/launcher/util/BytesStreamSucker.java new file mode 100644 index 0000000..8d5d672 --- /dev/null +++ b/jack-launcher/src/com/android/jack/launcher/util/BytesStreamSucker.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.launcher.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.annotation.Nonnull; + +/** + * Class that continuously read an {@link InputStream} and optionally could write the input in a + * {@link OutputStream}. + */ +public class BytesStreamSucker { + + private static final int BUFFER_SIZE = 4096; + + @Nonnull + private final byte[] buffer = new byte[BUFFER_SIZE]; + + @Nonnull + private final InputStream is; + + @Nonnull + private final OutputStream os; + + private final boolean toBeClose; + + public BytesStreamSucker( + @Nonnull InputStream is, @Nonnull OutputStream os, boolean toBeClose) { + this.is = is; + this.os = os; + this.toBeClose = toBeClose; + } + + public BytesStreamSucker(@Nonnull InputStream is, @Nonnull OutputStream os) { + this(is, os, false); + } + + public void suck() throws IOException { + try { + int bytesRead; + while ((bytesRead = is.read(buffer)) >= 0) { + os.write(buffer, 0, bytesRead); + os.flush(); + } + } finally { + if (toBeClose) { + os.close(); + } + } + } +}
\ No newline at end of file |