diff --git a/reviewboard/diffviewer/filediff_creator.py b/reviewboard/diffviewer/filediff_creator.py
index e9517d0b36b4b640a2638caf702fde9b96e5621d..201772bf9075a2c33bd0d83b448f41f41ebecd38 100644
--- a/reviewboard/diffviewer/filediff_creator.py
+++ b/reviewboard/diffviewer/filediff_creator.py
@@ -250,6 +250,9 @@ def create_filediffs(diff_file_contents, parent_diff_file_contents,
                 filediff.new_symlink_target = \
                     convert_to_unicode(f.new_symlink_target, encoding_list)[1]
 
+        filediff.old_unix_mode = f.old_unix_mode
+        filediff.new_unix_mode = f.new_unix_mode
+
         if not validate_only:
             # This state all requires making modifications to the database.
             # We only want to do this if we're saving.
diff --git a/reviewboard/diffviewer/models/filediff.py b/reviewboard/diffviewer/models/filediff.py
index b8e19e64526ee872d60d2155bd16e46ce6115803..d6e87679b98a37c93b8fe572731696d201438837 100644
--- a/reviewboard/diffviewer/models/filediff.py
+++ b/reviewboard/diffviewer/models/filediff.py
@@ -245,6 +245,80 @@ class FileDiff(models.Model):
         """
         self.extra_data['new_symlink_target'] = value
 
+    @property
+    def old_unix_mode(self):
+        """The old UNIX permissions mode for the file.
+
+        This represents the value of the ``old_unix_mode`` key in
+        :py:attr:`extra_data`. It is stored as a string representing an
+        octal.
+
+        Version Added:
+            4.0.6
+
+        Type:
+            unicode
+        """
+        return self.extra_data.get('old_unix_mode')
+
+    @old_unix_mode.setter
+    def old_unix_mode(self, value):
+        """Set the old UNIX permissions mode for the file.
+
+        This will set the ``old_unix_mode`` key in :py:attr:`extra_data`
+        to a string-encoded UNIX file mode.
+
+        If setting to ``None``, this will delete the key.
+
+        Version Added:
+            4.0.6
+
+        Args:
+            value (unicode):
+                The old UNIX mode.
+        """
+        if value is None:
+            self.extra_data.pop('old_unix_mode', None)
+        else:
+            self.extra_data['old_unix_mode'] = value
+
+    @property
+    def new_unix_mode(self):
+        """The new UNIX permissions mode for the file.
+
+        This represents the value of the ``new_unix_mode`` key in
+        :py:attr:`extra_data`. It is stored as a string representing an
+        octal.
+
+        Version Added:
+            4.0.6
+
+        Type:
+            unicode
+        """
+        return self.extra_data.get('new_unix_mode')
+
+    @new_unix_mode.setter
+    def new_unix_mode(self, value):
+        """Set the new UNIX permissions mode for the file.
+
+        This will set the ``new_unix_mode`` key in :py:attr:`extra_data`
+        to a string-encoded UNIX file mode.
+
+        If setting to ``None``, this will delete the key.
+
+        Version Added:
+            4.0.6
+
+        Args:
+            value (unicode):
+                The old UNIX mode.
+        """
+        if value is None:
+            self.extra_data.pop('new_unix_mode', None)
+        else:
+            self.extra_data['new_unix_mode'] = value
+
     @property
     def status_string(self):
         """The FileDiff's status as a human-readable string."""
diff --git a/reviewboard/diffviewer/parser.py b/reviewboard/diffviewer/parser.py
index 00bc4ee69ad9eae507b0166731bd1935af6b45fb..6354238b7ff9fa434d5440340cefb5fcb06500fa 100644
--- a/reviewboard/diffviewer/parser.py
+++ b/reviewboard/diffviewer/parser.py
@@ -266,6 +266,24 @@ class ParsedDiffFile(object):
     #:     bytes
     new_symlink_target = TypedProperty(bytes)
 
+    #: The old UNIX mode for the file.
+    #:
+    #: Version Added:
+    #:     4.0.6
+    #:
+    #: Type:
+    #:     int
+    old_unix_mode = TypedProperty(six.text_type)
+
+    #: The new UNIX mode for the file.
+    #:
+    #: Version Added:
+    #:     4.0.6
+    #:
+    #: Type:
+    #:     int
+    new_unix_mode = TypedProperty(six.text_type)
+
     #: The parsed original name of the file.
     #:
     #: Deprecated:
@@ -1606,6 +1624,32 @@ class DiffXParser(BaseDiffParser):
                             parsed_diff_file.new_symlink_target = \
                                 new_symlink_target
 
+                # If there are UNIX file modes, set them.
+                unix_mode = file_meta.get('unix file mode')
+
+                if unix_mode is not None:
+                    if isinstance(unix_mode, dict):
+                        old_unix_mode = unix_mode.get('old')
+                        new_unix_mode = unix_mode.get('new')
+                    elif isinstance(unix_mode, six.text_type):
+                        old_unix_mode = unix_mode
+                        new_unix_mode = unix_mode
+                    else:
+                        logger.warning('Unexpected UNIX file mode (%r) '
+                                       'found in diff %r',
+                                       unix_mode, self.data)
+                        old_unix_mode = None
+                        new_unix_mode = None
+
+                    if old_unix_mode or new_unix_mode:
+                        if op == 'create':
+                            parsed_diff_file.new_unix_mode = new_unix_mode
+                        elif op == 'delete':
+                            parsed_diff_file.old_unix_mode = old_unix_mode
+                        else:
+                            parsed_diff_file.new_unix_mode = new_unix_mode
+                            parsed_diff_file.old_unix_mode = old_unix_mode
+
                 parsed_diff_file.append_data(diff_data)
                 parsed_diff_file.finalize()
 
diff --git a/reviewboard/diffviewer/testing/mixins.py b/reviewboard/diffviewer/testing/mixins.py
index 357716c69265ce85912637a331e2f0e27ef8d8d8..33f7ac2921042f484f5b698b8e65e26ed9ac8f3c 100644
--- a/reviewboard/diffviewer/testing/mixins.py
+++ b/reviewboard/diffviewer/testing/mixins.py
@@ -98,7 +98,9 @@ class DiffParserTestingMixin(object):
                 'is_symlink': False,
                 'moved': False,
                 'new_symlink_target': None,
+                'new_unix_mode': None,
                 'old_symlink_target': None,
+                'old_unix_mode': None,
             })
 
         self.assertEqual(parsed_diff_file.data.splitlines(),
diff --git a/reviewboard/diffviewer/tests/test_diffx_parser.py b/reviewboard/diffviewer/tests/test_diffx_parser.py
index 839deb174878ba4667f72932089abf23d4f62e7f..f0ba572da8f8842c15c153f0719231bc233fff92 100644
--- a/reviewboard/diffviewer/tests/test_diffx_parser.py
+++ b/reviewboard/diffviewer/tests/test_diffx_parser.py
@@ -1750,6 +1750,388 @@ class DiffXParserTests(DiffParserTestingMixin, TestCase):
                 },
             })
 
+    def test_parse_diff_with_unix_file_mode_op_create_target_str(self):
+        """Testing DiffXParser.parse_diff with op=create, unix file
+        mode=string
+        """
+        parser = DiffXParser(
+            b'#diffx: encoding=utf-8, version=1.0\n'
+            b'#.change:\n'
+            b'#..file:\n'
+            b'#...meta: format=json, length=125\n'
+            b'{\n'
+            b'    "op": "create",\n'
+            b'    "path": "name",\n'
+            b'    "revision": {\n'
+            b'        "new": "def456"\n'
+            b'    },\n'
+            b'    "unix file mode": "0100644"\n'
+            b'}\n'
+        )
+
+        parsed_diff = parser.parse_diff()
+        self.assert_parsed_diff(
+            parsed_diff,
+            parser=parser,
+            num_changes=1,
+            extra_data={
+                'diffx': {
+                    'options': {
+                        'encoding': 'utf-8',
+                        'version': '1.0',
+                    },
+                },
+            })
+
+        parsed_change = parsed_diff.changes[0]
+        self.assert_parsed_diff_change(parsed_change,
+                                       num_files=1)
+
+        self.assert_parsed_diff_file(
+            parsed_change.files[0],
+            orig_filename=b'name',
+            orig_file_details=PRE_CREATION,
+            modified_filename=b'name',
+            modified_file_details=b'def456',
+            new_unix_mode='0100644',
+            extra_data={
+                'diffx': {
+                    'metadata': {
+                        'op': 'create',
+                        'path': 'name',
+                        'revision': {
+                            'new': 'def456',
+                        },
+                        'unix file mode': '0100644',
+                    },
+                    'metadata_options': {
+                        'format': 'json',
+                    },
+                },
+            })
+
+    def test_parse_diff_with_unix_file_mode_op_create_target_dict(self):
+        """Testing DiffXParser.parse_diff with op=create, unix file
+        mode=dict
+        """
+        parser = DiffXParser(
+            b'#diffx: encoding=utf-8, version=1.0\n'
+            b'#.change:\n'
+            b'#..file:\n'
+            b'#...meta: format=json, length=148\n'
+            b'{\n'
+            b'    "op": "create",\n'
+            b'    "path": "name",\n'
+            b'    "revision": {\n'
+            b'        "new": "def456"\n'
+            b'    },\n'
+            b'    "unix file mode": {\n'
+            b'        "new": "0100644"\n'
+            b'    }\n'
+            b'}\n'
+        )
+
+        parsed_diff = parser.parse_diff()
+        self.assert_parsed_diff(
+            parsed_diff,
+            parser=parser,
+            num_changes=1,
+            extra_data={
+                'diffx': {
+                    'options': {
+                        'encoding': 'utf-8',
+                        'version': '1.0',
+                    },
+                },
+            })
+
+        parsed_change = parsed_diff.changes[0]
+        self.assert_parsed_diff_change(parsed_change,
+                                       num_files=1)
+
+        self.assert_parsed_diff_file(
+            parsed_change.files[0],
+            orig_filename=b'name',
+            orig_file_details=PRE_CREATION,
+            modified_filename=b'name',
+            modified_file_details=b'def456',
+            new_unix_mode='0100644',
+            extra_data={
+                'diffx': {
+                    'metadata': {
+                        'op': 'create',
+                        'path': 'name',
+                        'revision': {
+                            'new': 'def456',
+                        },
+                        'unix file mode': {
+                            'new': '0100644',
+                        },
+                    },
+                    'metadata_options': {
+                        'format': 'json',
+                    },
+                },
+            })
+
+    def test_parse_diff_with_unix_file_mode_op_modify_target_str(self):
+        """Testing DiffXParser.parse_diff with op=modify, unix file
+        mode=string
+        """
+        parser = DiffXParser(
+            b'#diffx: encoding=utf-8, version=1.0\n'
+            b'#.change:\n'
+            b'#..file:\n'
+            b'#...meta: format=json, length=150\n'
+            b'{\n'
+            b'    "op": "modify",\n'
+            b'    "path": "name",\n'
+            b'    "revision": {\n'
+            b'        "old": "abc123",\n'
+            b'        "new": "def456"\n'
+            b'    },\n'
+            b'    "unix file mode": "0100644"\n'
+            b'}\n'
+        )
+
+        parsed_diff = parser.parse_diff()
+        self.assert_parsed_diff(
+            parsed_diff,
+            parser=parser,
+            num_changes=1,
+            extra_data={
+                'diffx': {
+                    'options': {
+                        'encoding': 'utf-8',
+                        'version': '1.0',
+                    },
+                },
+            })
+
+        parsed_change = parsed_diff.changes[0]
+        self.assert_parsed_diff_change(parsed_change,
+                                       num_files=1)
+
+        self.assert_parsed_diff_file(
+            parsed_change.files[0],
+            orig_filename=b'name',
+            orig_file_details=b'abc123',
+            modified_filename=b'name',
+            modified_file_details=b'def456',
+            old_unix_mode='0100644',
+            new_unix_mode='0100644',
+            extra_data={
+                'diffx': {
+                    'metadata': {
+                        'op': 'modify',
+                        'path': 'name',
+                        'revision': {
+                            'old': 'abc123',
+                            'new': 'def456',
+                        },
+                        'unix file mode': '0100644',
+                    },
+                    'metadata_options': {
+                        'format': 'json',
+                    },
+                },
+            })
+
+    def test_parse_diff_with_unix_file_mode_op_modify_target_dict(self):
+        """Testing DiffXParser.parse_diff with op=modify, unix file
+        mode=dict
+        """
+        parser = DiffXParser(
+            b'#diffx: encoding=utf-8, version=1.0\n'
+            b'#.change:\n'
+            b'#..file:\n'
+            b'#...meta: format=json, length=199\n'
+            b'{\n'
+            b'    "op": "modify",\n'
+            b'    "path": "name",\n'
+            b'    "revision": {\n'
+            b'        "old": "abc123",\n'
+            b'        "new": "def456"\n'
+            b'    },\n'
+            b'    "unix file mode": {\n'
+            b'        "old": "0100644",\n'
+            b'        "new": "0100755"\n'
+            b'    }\n'
+            b'}\n'
+        )
+
+        parsed_diff = parser.parse_diff()
+        self.assert_parsed_diff(
+            parsed_diff,
+            parser=parser,
+            num_changes=1,
+            extra_data={
+                'diffx': {
+                    'options': {
+                        'encoding': 'utf-8',
+                        'version': '1.0',
+                    },
+                },
+            })
+
+        parsed_change = parsed_diff.changes[0]
+        self.assert_parsed_diff_change(parsed_change,
+                                       num_files=1)
+
+        self.assert_parsed_diff_file(
+            parsed_change.files[0],
+            orig_filename=b'name',
+            orig_file_details=b'abc123',
+            modified_filename=b'name',
+            modified_file_details=b'def456',
+            old_unix_mode='0100644',
+            new_unix_mode='0100755',
+            extra_data={
+                'diffx': {
+                    'metadata': {
+                        'op': 'modify',
+                        'path': 'name',
+                        'revision': {
+                            'old': 'abc123',
+                            'new': 'def456',
+                        },
+                        'unix file mode': {
+                            'old': '0100644',
+                            'new': '0100755',
+                        },
+                    },
+                    'metadata_options': {
+                        'format': 'json',
+                    },
+                },
+            })
+
+    def test_parse_diff_with_unix_file_mode_op_delete_target_str(self):
+        """Testing DiffXParser.parse_diff with op=delete, unix file
+        mode=string
+        """
+        parser = DiffXParser(
+            b'#diffx: encoding=utf-8, version=1.0\n'
+            b'#.change:\n'
+            b'#..file:\n'
+            b'#...meta: format=json, length=125\n'
+            b'{\n'
+            b'    "op": "delete",\n'
+            b'    "path": "name",\n'
+            b'    "revision": {\n'
+            b'        "old": "abc123"\n'
+            b'    },\n'
+            b'    "unix file mode": "0100644"\n'
+            b'}\n'
+        )
+
+        parsed_diff = parser.parse_diff()
+        self.assert_parsed_diff(
+            parsed_diff,
+            parser=parser,
+            num_changes=1,
+            extra_data={
+                'diffx': {
+                    'options': {
+                        'encoding': 'utf-8',
+                        'version': '1.0',
+                    },
+                },
+            })
+
+        parsed_change = parsed_diff.changes[0]
+        self.assert_parsed_diff_change(parsed_change,
+                                       num_files=1)
+
+        self.assert_parsed_diff_file(
+            parsed_change.files[0],
+            orig_filename=b'name',
+            orig_file_details=b'abc123',
+            modified_filename=b'name',
+            modified_file_details=HEAD,
+            old_unix_mode='0100644',
+            deleted=True,
+            extra_data={
+                'diffx': {
+                    'metadata': {
+                        'op': 'delete',
+                        'path': 'name',
+                        'revision': {
+                            'old': 'abc123',
+                        },
+                        'unix file mode': '0100644',
+                    },
+                    'metadata_options': {
+                        'format': 'json',
+                    },
+                },
+            })
+
+    def test_parse_diff_with_unix_file_mode_op_delete_target_dict(self):
+        """Testing DiffXParser.parse_diff with op=delete, unix file
+        mode=dict
+        """
+        parser = DiffXParser(
+            b'#diffx: encoding=utf-8, version=1.0\n'
+            b'#.change:\n'
+            b'#..file:\n'
+            b'#...meta: format=json, length=148\n'
+            b'{\n'
+            b'    "op": "delete",\n'
+            b'    "path": "name",\n'
+            b'    "revision": {\n'
+            b'        "old": "abc123"\n'
+            b'    },\n'
+            b'    "unix file mode": {\n'
+            b'        "old": "0100644"\n'
+            b'    }\n'
+            b'}\n'
+        )
+
+        parsed_diff = parser.parse_diff()
+        self.assert_parsed_diff(
+            parsed_diff,
+            parser=parser,
+            num_changes=1,
+            extra_data={
+                'diffx': {
+                    'options': {
+                        'encoding': 'utf-8',
+                        'version': '1.0',
+                    },
+                },
+            })
+
+        parsed_change = parsed_diff.changes[0]
+        self.assert_parsed_diff_change(parsed_change,
+                                       num_files=1)
+
+        self.assert_parsed_diff_file(
+            parsed_change.files[0],
+            orig_filename=b'name',
+            orig_file_details=b'abc123',
+            modified_filename=b'name',
+            modified_file_details=HEAD,
+            old_unix_mode='0100644',
+            deleted=True,
+            extra_data={
+                'diffx': {
+                    'metadata': {
+                        'op': 'delete',
+                        'path': 'name',
+                        'revision': {
+                            'old': 'abc123',
+                        },
+                        'unix file mode': {
+                            'old': '0100644',
+                        },
+                    },
+                    'metadata_options': {
+                        'format': 'json',
+                    },
+                },
+            })
+
     def test_parse_diff_with_invalid_diffx(self):
         """Testing DiffXParser.parse_diff with invalid DiffX file contents"""
         parser = DiffXParser(
diff --git a/reviewboard/diffviewer/tests/test_filediff.py b/reviewboard/diffviewer/tests/test_filediff.py
index 88608012df5d4d1bb8af2b520174622006ff8b33..63cb4e44d1072c54e25b0f9afb40647b0d26768e 100644
--- a/reviewboard/diffviewer/tests/test_filediff.py
+++ b/reviewboard/diffviewer/tests/test_filediff.py
@@ -269,6 +269,22 @@ class FileDiffTests(TestCase):
         self.assertEqual(filediff.extra_data.get('new_symlink_target'),
                          'new/path')
 
+    def test_old_unix_mode(self):
+        """Testing FileDiff.old_unix_mode"""
+        filediff = self.create_filediff(self.diffset)
+        filediff.old_unix_mode = '0100644'
+
+        self.assertEqual(filediff.old_unix_mode, '0100644')
+        self.assertEqual(filediff.extra_data.get('old_unix_mode'), '0100644')
+
+    def test_new_unix_mode(self):
+        """Testing FileDiff.new_unix_mode"""
+        filediff = self.create_filediff(self.diffset)
+        filediff.new_unix_mode = '0100750'
+
+        self.assertEqual(filediff.new_unix_mode, '0100750')
+        self.assertEqual(filediff.extra_data.get('new_unix_mode'), '0100750')
+
 
 class FileDiffAncestorTests(BaseFileDiffAncestorTests):
     """Unit tests for FileDiff.get_ancestors"""
diff --git a/reviewboard/diffviewer/tests/test_filediff_creator.py b/reviewboard/diffviewer/tests/test_filediff_creator.py
index 3fddeb4b0b3afceb9e2453c5e21cd561d36171ef..77308b07d87af5ec90827418362bd24b0e2c42c1 100644
--- a/reviewboard/diffviewer/tests/test_filediff_creator.py
+++ b/reviewboard/diffviewer/tests/test_filediff_creator.py
@@ -175,3 +175,44 @@ class FileDiffCreatorTests(TestCase):
         self.assertTrue(filediff.is_symlink)
         self.assertEqual(filediff.old_symlink_target, 'old/target/')
         self.assertEqual(filediff.new_symlink_target, 'new/target/')
+
+    def test_create_filediffs_with_unix_mode(self):
+        """Testing create_filediffs() with UNIX file modes"""
+        repository = self.create_repository(tool_name='TestToolDiffX')
+        diffset = self.create_diffset(repository=repository)
+
+        self.assertEqual(diffset.files.count(), 0)
+
+        create_filediffs(
+            diff_file_contents=(
+                b'#diffx: encoding=utf-8, version=1.0\n'
+                b'#.change:\n'
+                b'#..file:\n'
+                b'#...meta: format=json, length=199\n'
+                b'{\n'
+                b'    "op": "modify",\n'
+                b'    "path": "name",\n'
+                b'    "revision": {\n'
+                b'        "old": "abc123",\n'
+                b'        "new": "def456"\n'
+                b'    },\n'
+                b'    "unix file mode": {\n'
+                b'        "old": "0100644",\n'
+                b'        "new": "0100755"\n'
+                b'    }\n'
+                b'}\n'
+            ),
+            parent_diff_file_contents=None,
+            repository=repository,
+            basedir='/',
+            base_commit_id='0' * 40,
+            diffset=diffset,
+            check_existence=False)
+
+        diffset = DiffSet.objects.get(pk=diffset.pk)
+
+        self.assertEqual(diffset.files.count(), 1)
+        filediff = diffset.files.get()
+
+        self.assertEqual(filediff.old_unix_mode, '0100644')
+        self.assertEqual(filediff.new_unix_mode, '0100755')
