Java security coding guide: file IO operation

brief introduction

We often use IO operations for files. Because of the complexity of files, we also need to pay attention to many places when using file operations. Let me take a look.

Specify appropriate permissions when creating files

Whether in windows or Linux, files have the concept of permission control. We can set the owner and permission of files. If the file permissions are not well controlled, malicious users may operate our files maliciously.

Therefore, we need to consider the issue of permissions when creating files.

Unfortunately, Java is not good at file operation, so in jdk1 Before 6, Java's IO operation was very weak. Basic file operation classes, such as fileoutputstream and filewriter, did not have permission options.

Writer out = new FileWriter("file");

So how?

At jdk1 Before 6, we need to use some local methods to modify permissions.

At jdk1 After 6, Java introduced NiO, which can control the permission function of files through some features of NiO.

Let's take a look at the CreateFile method of the files tool class:

    public static Path createFile(Path path,FileAttribute<?>... attrs)
        throws IOException
    {
        newByteChannel(path,DEFAULT_CREATE_OPTIONS,attrs).close();
        return path;
    }

Fileattribute is the attribute of the file. Let's see how to specify the permission of the file:

    public void createFileWithPermission() throws IOException {
        Set<PosixFilePermission> perms =
                PosixFilePermissions.fromString("rw-------");
        FileAttribute<Set<PosixFilePermission>> attr =
                PosixFilePermissions.asFileAttribute(perms);
        Path file = new File("/tmp/www.flydean.com").toPath();
        Files.createFile(file,attr);
    }

Check the return value of the file operation

Many file operations in java have return values, such as file Delete(), we need to judge whether the file operation is completed according to the return value, so don't ignore the return value.

Delete used temporary files

If we use a file that does not need to be permanently stored, we can easily use the createtempfile of file to create a temporary file. The name of the temporary file is generated randomly. We want to delete the temporary file after it is used.

How to delete it? File provides a deleteonexit method, which will delete the file when the JVM exits.

Let's look at the following example:

    public void wrongDelete() throws IOException {
        File f = File.createTempFile("tmpfile",".tmp");
        FileOutputStream fop = null;
        try {
            fop = new FileOutputStream(f);
            String str = "Data";
            fop.write(str.getBytes());
            fop.flush();
        } finally {
            // 因为Stream没有被关闭,所以文件在windows平台上面不会被删除
            f.deleteOnExit(); // 在JVM退出的时候删除临时文件

            if (fop != null) {
                try {
                    fop.close();
                } catch (IOException x) {
                    // Handle error
                }
            }
        }
    }

In the example above, we created a temporary file and called the deleteOnExit method in finally, but because Stream was not closed when calling this method, the file was not deleted on the windows platform.

How to solve it?

NiO provides a delete_ ON_ The close option ensures that the file will be deleted after closing:

    public void correctDelete() throws IOException {
        Path tempFile = null;
            tempFile = Files.createTempFile("tmpfile",".tmp");
            try (BufferedWriter writer =
                         Files.newBufferedWriter(tempFile,Charset.forName("UTF8"),StandardOpenOption.DELETE_ON_CLOSE)) {
                // Write to file
            }
        }

In the above example, we added the standardopenoption DELETE_ ON_ Close, the file will be deleted after the writer is closed.

Free resources that are no longer in use

If resources are no longer used, we need to remember to close them, otherwise it will cause resource leakage.

But many times we may forget to close, so what should we do? JDK7 introduces a try with resources mechanism. As long as the resources that implement the closeable interface are placed in the try statement, they will be automatically closed, which is very convenient.

Pay attention to the security of buffer

NiO provides many very useful buffer classes, such as intbuffer, charbuffer and ByteBuffer. These buffers actually encapsulate the underlying array. Although a new buffer object is created, this buffer is associated with the underlying array, so do not easily expose the buffer, otherwise the underlying array may be modified.

    public CharBuffer getBuffer(){
         char[] dataArray = new char[10];
         return CharBuffer.wrap(dataArray);
    }

The above example exposes the charbuffer and actually the underlying char array.

There are two ways to improve it:

    public CharBuffer getBuffer1(){
        char[] dataArray = new char[10];
        return CharBuffer.wrap(dataArray).asReadOnlyBuffer();
    }

The first way is to convert charbuffer to read-only.

The second way is to create a new buffer and cut off the connection between the buffer and the array:

    public CharBuffer getBuffer2(){
        char[] dataArray = new char[10];
        CharBuffer cb = CharBuffer.allocate(dataArray.length);
        cb.put(dataArray);
        return cb;
    }

Note the standard input and output of process

In Java, you can use runtime Exec() to execute native commands, while runtime Exec () has a return value. Its return value is a process object used to control and obtain the execution information of the native program.

By default, the created process does not have its own I / O stream, which means that the process uses the I / O (stdin, stdout, stderr) of the parent process. Process provides the following three methods to obtain I / O:

getOutputStream()
getInputStream()
getErrorStream()

If parent process IO is used, the buffer space on some systems is relatively small. If a large number of input and output operations occur, they may be blocked or even deadlock.

What shall I do? All we need to do is process the IO generated by process to prevent buffer blocking.

public class StreamProcesser implements Runnable{
    private final InputStream is;
    private final PrintStream os;

    StreamProcesser(InputStream is,PrintStream os){
        this.is=is;
        this.os=os;
    }

    @Override
    public void run() {
        try {
            int c;
            while ((c = is.read()) != -1)
                os.print((char) c);
        } catch (IOException x) {
            // Handle error
        }
    }

    public static void main(String[] args) throws IOException,InterruptedException {
        Runtime rt = Runtime.getRuntime();
        Process proc = rt.exec("vscode");

        Thread errorGobbler
                = new Thread(new StreamProcesser(proc.getErrorStream(),System.err));

        Thread outputGobbler
                = new Thread(new StreamProcesser(proc.getInputStream(),System.out));

        errorGobbler.start();
        outputGobbler.start();

        int exitVal = proc.waitFor();
        errorGobbler.join();
        outputGobbler.join();
    }
}

In the above example, we created a streamprocessor to process the error and input of the process.

InputStream. Read() and reader read()

Both InputStream and reader have a read () method. The difference between the two methods is that InputStream read is byte and reader read is char.

Although the byte range is - 128 to 127, InputStream Read() will convert the bytes read into ints in the range of 0-255 (0x00-0xff).

The range of char is 0x0000-0xffff, reader Read() will return the same range of int values: 0x0000-0xffff.

If the return value is - 1, it means that the stream has ended. Here, the int of - 1 is 0xFFFFFFFF.

In the process of using, we need to judge the read return value to distinguish the boundary of the stream.

We consider such a question:

FileInputStream in;
byte data;
while ((data = (byte) in.read()) != -1) {
}

Above, we first convert the read result of InputStream into byte, and then judge whether it is equal to - 1. What's the problem?

If the value of byte itself is 0xff, which is a - 1, but InputStream converts it into an int in the range of 0-255 after reading, the int value after conversion is 0x000000ff. If byte conversion is performed again, the last oxff will be intercepted, oxff = = - 1, which will eventually lead to the end of the wrong judgment stream.

Therefore, we need to judge the return value before conversion:

FileInputStream in;
int inbuff;
byte data;
while ((inbuff = in.read()) != -1) {
  data = (byte) inbuff;
  // ... 
}

Similarly, in the following example, if you use char to convert int in advance, because the range of char is unsigned, it can never be equal to - 1

FileReader in;
char data;
while ((data = (char) in.read()) != -1) {
  // ...
}

The write () method should not be out of range

A very strange method in OutputStream is write. Let's see the definition of write method:

    public abstract void write(int b) throws IOException;

Write receives an int parameter, but actually writes a byte.

Because the range of int and byte is different, the incoming int will be intercepted and converted into a byte.

Therefore, we must judge the write range when using:

    public void writeInt(int value){
        int intValue = Integer.valueOf(value);
        if (intValue < 0 || intValue > 255) {
            throw new ArithmeticException("Value超出范围");
        }
        System.out.write(value);
        System.out.flush();
    }

Or some stream operations can be directly writeint, which we can call directly.

Note the use of read with array

InputStream has two read methods with arrays:

public int read(byte b[]) throws IOException

and

public int read(byte b[],int off,int len) throws IOException

If we use these two methods, we must pay attention to whether the byte array read is filled. Consider the following example:

    public String wrongRead(InputStream in) throws IOException {
        byte[] data = new byte[1024];
        if (in.read(data) == -1) {
            throw new EOFException();
        }
        return new String(data,"UTF-8");
    }

If the InputStream data is not 1024, or 1024 is not filled up due to network reasons, we will get an array that is not filled up, so we actually have a problem in using it.

How to use it correctly?

    public String readArray(InputStream in) throws IOException {
        int offset = 0;
        int bytesRead = 0;
        byte[] data = new byte[1024];
        while ((bytesRead = in.read(data,offset,data.length - offset))
                != -1) {
            offset += bytesRead;
            if (offset >= data.length) {
                break;
            }
        }
        String str = new String(data,"UTF-8");
        return str;
    }

We need to record the number of bytes actually read. By recording the offset, we get the final actual read result.

Or we can use the readfull method of datainputstream to ensure that the complete byte array is read.

The problems of little endian and big endian

Data in Java is stored in big endian mode by default. Readbyte(), readshort(), readint(), readlong(), readfloat(), and readdouble() in datainputstream are also read in big endian mode by default. Problems may occur when interacting with other small endians.

What we need is to convert little endian into big endian.

How to convert?

For example, if we want to read an int, we can first use the read method to read 4 bytes, and then convert the read 4 bytes from little endian to big endian.

    public void method1(InputStream inputStream) throws IOException {
        try(DataInputStream dis = new DataInputStream(inputStream)) {
            byte[] buffer = new byte[4];
            int bytesRead = dis.read(buffer);  // Bytes are read into buffer
            if (bytesRead != 4) {
                throw new IOException("Unexpected End of Stream");
            }
            int serialNumber =
                    ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
        }
    }

In the above example, we use the wrap and order methods provided by ByteBuffer to convert byte arrays.

Of course, we can also convert manually.

Another simplest method is to call jdk1 Reverse bytes () after 5 directly converts the small end to the large end.

    public  int reverse(int i) {
        return Integer.reverseBytes(i);
    }

Code for this article:

learn-java-base-9-to-20/tree/master/security

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>