1/*
2 * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25#include <sys/types.h>
26#include <sys/stat.h>
27#include <door.h>
28#include <stdlib.h>
29#include <unistd.h>
30#include <signal.h>
31#include <string.h>
32#include <fcntl.h>
33#include <errno.h>
34#include <limits.h>
35
36#include "jni.h"
37#include "jni_util.h"
38#include "jvm.h"
39
40#include "sun_tools_attach_VirtualMachineImpl.h"
41
42#define RESTARTABLE(_cmd, _result) do { \
43  do { \
44    _result = _cmd; \
45  } while((_result == -1) && (errno == EINTR)); \
46} while(0)
47
48/*
49 * Declare library specific JNI_Onload entry if static build
50 */
51DEF_STATIC_JNI_OnLoad
52
53/*
54 * Class:     sun_tools_attach_VirtualMachineImpl
55 * Method:    open
56 * Signature: (Ljava/lang/String;)I
57 */
58JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_open
59  (JNIEnv *env, jclass cls, jstring path)
60{
61    jboolean isCopy;
62    const char* p = GetStringPlatformChars(env, path, &isCopy);
63    if (p == NULL) {
64        return 0;
65    } else {
66        int fd;
67        int err = 0;
68
69        fd = open(p, O_RDWR);
70        if (fd == -1) {
71            err = errno;
72        }
73
74        if (isCopy) {
75            JNU_ReleaseStringPlatformChars(env, path, p);
76        }
77
78        if (fd == -1) {
79            if (err == ENOENT) {
80                JNU_ThrowByName(env, "java/io/FileNotFoundException", NULL);
81            } else {
82                char* msg = strdup(strerror(err));
83                JNU_ThrowIOException(env, msg);
84                if (msg != NULL) {
85                    free(msg);
86                }
87            }
88        }
89        return fd;
90    }
91}
92
93/*
94 * Class:     sun_tools_attach_VirtualMachineImpl
95 * Method:    checkPermissions
96 * Signature: (Ljava/lang/String;)V
97 */
98JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions
99  (JNIEnv *env, jclass cls, jstring path)
100{
101    jboolean isCopy;
102    const char* p = GetStringPlatformChars(env, path, &isCopy);
103    if (p != NULL) {
104        struct stat64 sb;
105        uid_t uid, gid;
106        int res;
107
108        /*
109         * Check that the path is owned by the effective uid/gid of this
110         * process. Also check that group/other access is not allowed.
111         */
112        uid = geteuid();
113        gid = getegid();
114
115        res = stat64(p, &sb);
116        if (res != 0) {
117            /* save errno */
118            res = errno;
119        }
120
121        if (res == 0) {
122            char msg[100];
123            jboolean isError = JNI_FALSE;
124            if (sb.st_uid != uid) {
125                jio_snprintf(msg, sizeof(msg)-1,
126                    "file should be owned by the current user (which is %d) but is owned by %d", uid, sb.st_uid);
127                isError = JNI_TRUE;
128            } else if (sb.st_gid != gid) {
129                jio_snprintf(msg, sizeof(msg)-1,
130                    "file's group should be the current group (which is %d) but the group is %d", gid, sb.st_gid);
131                isError = JNI_TRUE;
132            } else if ((sb.st_mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != 0) {
133                jio_snprintf(msg, sizeof(msg)-1,
134                    "file should only be readable and writable by the owner but has 0%03o access", sb.st_mode & 0777);
135                isError = JNI_TRUE;
136            }
137            if (isError) {
138                char buf[256];
139                jio_snprintf(buf, sizeof(buf)-1, "well-known file %s is not secure: %s", p, msg);
140                JNU_ThrowIOException(env, buf);
141            }
142        } else {
143            char* msg = strdup(strerror(res));
144            JNU_ThrowIOException(env, msg);
145            if (msg != NULL) {
146                free(msg);
147            }
148        }
149
150        if (isCopy) {
151            JNU_ReleaseStringPlatformChars(env, path, p);
152        }
153    }
154}
155
156/*
157 * Class:     sun_tools_attach_VirtualMachineImpl
158 * Method:    close
159 * Signature: (I)V
160 */
161JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_close
162  (JNIEnv *env, jclass cls, jint fd)
163{
164    int ret;
165    RESTARTABLE(close(fd), ret);
166}
167
168/*
169 * Class:     sun_tools_attach_VirtualMachineImpl
170 * Method:    read
171 * Signature: (I[BI)I
172 */
173JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_read
174  (JNIEnv *env, jclass cls, jint fd, jbyteArray ba, jint off, jint baLen)
175{
176    unsigned char buf[128];
177    size_t len = sizeof(buf);
178    ssize_t n;
179
180    size_t remaining = (size_t)(baLen - off);
181    if (len > remaining) {
182        len = remaining;
183    }
184
185    RESTARTABLE(read(fd, buf, len), n);
186    if (n == -1) {
187        JNU_ThrowIOExceptionWithLastError(env, "read");
188    } else {
189        if (n == 0) {
190            n = -1;     // EOF
191        } else {
192            (*env)->SetByteArrayRegion(env, ba, off, (jint)n, (jbyte *)(buf));
193        }
194    }
195    return n;
196}
197
198/*
199 * Class:     sun_tools_attach_VirtualMachineImpl
200 * Method:    sigquit
201 * Signature: (I)V
202 */
203JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_sigquit
204  (JNIEnv *env, jclass cls, jint pid)
205{
206    if (kill((pid_t)pid, SIGQUIT) == -1) {
207        JNU_ThrowIOExceptionWithLastError(env, "kill");
208    }
209}
210
211/*
212 * A simple table to translate some known errors into reasonable
213 * error messages
214 */
215static struct {
216    jint err;
217    const char* msg;
218} const error_messages[] = {
219    { 100,      "Bad request" },
220    { 101,      "Protocol mismatch" },
221    { 102,      "Resource failure" },
222    { 103,      "Internal error" },
223    { 104,      "Permission denied" },
224};
225
226/*
227 * Lookup the given error code and return the appropriate
228 * message. If not found return NULL.
229 */
230static const char* translate_error(jint err) {
231    int table_size = sizeof(error_messages) / sizeof(error_messages[0]);
232    int i;
233
234    for (i=0; i<table_size; i++) {
235        if (err == error_messages[i].err) {
236            return error_messages[i].msg;
237        }
238    }
239    return NULL;
240}
241
242/*
243 * Current protocol version
244 */
245static const char* PROTOCOL_VERSION = "1";
246
247/*
248 * Class:     sun_tools_attach_VirtualMachineImpl
249 * Method:    enqueue
250 * Signature: (JILjava/lang/String;[Ljava/lang/Object;)V
251 */
252JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_enqueue
253  (JNIEnv *env, jclass cls, jint fd, jstring cmd, jobjectArray args)
254{
255    jint arg_count, i;
256    size_t size;
257    jboolean isCopy;
258    door_arg_t door_args;
259    char res_buffer[128];
260    jint result = -1;
261    int rc;
262    const char* cstr;
263    char* buf;
264
265    /*
266     * First we get the command string and create the start of the
267     * argument string to send to the target VM:
268     * <ver>\0<cmd>\0
269     */
270    cstr = JNU_GetStringPlatformChars(env, cmd, &isCopy);
271    if (cstr == NULL) {
272        return -1;              /* pending exception */
273    }
274    size = strlen(PROTOCOL_VERSION) + strlen(cstr) + 2;
275    buf = (char*)malloc(size);
276    if (buf != NULL) {
277        char* pos = buf;
278        strcpy(buf, PROTOCOL_VERSION);
279        pos += strlen(PROTOCOL_VERSION)+1;
280        strcpy(pos, cstr);
281    }
282    if (isCopy) {
283        JNU_ReleaseStringPlatformChars(env, cmd, cstr);
284    }
285    if (buf == NULL) {
286        JNU_ThrowOutOfMemoryError(env, "malloc failed");
287        return -1;
288    }
289
290    /*
291     * Next we iterate over the arguments and extend the buffer
292     * to include them.
293     */
294    arg_count = (*env)->GetArrayLength(env, args);
295
296    for (i=0; i<arg_count; i++) {
297        jobject obj = (*env)->GetObjectArrayElement(env, args, i);
298        if (obj != NULL) {
299            cstr = JNU_GetStringPlatformChars(env, obj, &isCopy);
300            if (cstr != NULL) {
301                size_t len = strlen(cstr);
302                char* newbuf = (char*)realloc(buf, size+len+1);
303                if (newbuf != NULL) {
304                    buf = newbuf;
305                    strcpy(buf+size, cstr);
306                    size += len+1;
307                }
308                if (isCopy) {
309                    JNU_ReleaseStringPlatformChars(env, obj, cstr);
310                }
311                if (newbuf == NULL) {
312                    free(buf);
313                    JNU_ThrowOutOfMemoryError(env, "realloc failed");
314                    return -1;
315                }
316            }
317        }
318        if ((*env)->ExceptionOccurred(env)) {
319            free(buf);
320            return -1;
321        }
322    }
323
324    /*
325     * The arguments to the door function are in 'buf' so we now
326     * do the door call
327     */
328    door_args.data_ptr = buf;
329    door_args.data_size = size;
330    door_args.desc_ptr = NULL;
331    door_args.desc_num = 0;
332    door_args.rbuf = (char*)&res_buffer;
333    door_args.rsize = sizeof(res_buffer);
334
335    RESTARTABLE(door_call(fd, &door_args), rc);
336
337    /*
338     * door_call failed
339     */
340    if (rc == -1) {
341        JNU_ThrowIOExceptionWithLastError(env, "door_call");
342    } else {
343        /*
344         * door_call succeeded but the call didn't return the expected jint.
345         */
346        if (door_args.data_size < sizeof(jint)) {
347            JNU_ThrowIOException(env, "Enqueue error - reason unknown as result is truncated!");
348        } else {
349            jint* res = (jint*)(door_args.data_ptr);
350            if (*res != JNI_OK) {
351                const char* msg = translate_error(*res);
352                char buf[255];
353                if (msg == NULL) {
354                    sprintf(buf, "Unable to enqueue command to target VM: %d", *res);
355                } else {
356                    sprintf(buf, "Unable to enqueue command to target VM: %s", msg);
357                }
358                JNU_ThrowIOException(env, buf);
359            } else {
360                /*
361                 * The door call should return a file descriptor to one end of
362                 * a socket pair
363                 */
364                if ((door_args.desc_ptr != NULL) &&
365                    (door_args.desc_num == 1) &&
366                    (door_args.desc_ptr->d_attributes & DOOR_DESCRIPTOR)) {
367                    result = door_args.desc_ptr->d_data.d_desc.d_descriptor;
368                } else {
369                    JNU_ThrowIOException(env, "Reply from enqueue missing descriptor!");
370                }
371            }
372        }
373    }
374
375    free(buf);
376    return result;
377}
378