SEAndroid安全机制中的文件安全上下文关联分析

发表于 5年以前  | 总阅读数:3688 次

前面一篇文章提到,SEAndroid是一种基于安全策略的MAC安全机制。这种安全策略实施在主体和客体的安全上下文之上。这意味着安全策略在实施之前,SEAndroid安全机制中的主休和客体是已经有安全上下文的。在SEAndroid安全机制中,主体一般就是进程,而客体一般就是文件。文件的安全上下文的关联有不同的方式。本文主要分析文件安全上下文的设置过程,接下来的一篇文章再分析进程安全上下文的设置过程。

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

在SEAndroid中,文件的安全上下文是在文件的创建过程中设置的。在Android系统中,文件的产生方式主要分为两种,一种是预置在ROM里面的,另外一种是动态创建的,即在系统在运行的过程中创建的。对于预置在ROM里面的文件,例如打包在system.img里面的文件,它们的安全上下文在是制作ROM的过程中设置的。而对于动态创建的文件,它们的安全上下文如果没有特别指定,就与父目录的安全上下文一致。

假设动态创建的文件的安全上下文来自于父目录。这时候就有一个问题需要解决,就是最开始的父目录的安全上下文是怎么来的呢?如果最开始的父目录是预置在ROM里面的,那么这个问题就很好解决。但是有一些目录,它们并不是预置在ROM的,而是在系统启动或者运行的过程中动态安装的,即我们平时所说的虚拟文件系统,例如我们在前面一篇文章SEAndroid安全机制框架分析中提到的selinux文件系统。这些虚拟文件系统在安装的时候,SEAndroid安全机制会根据安全策略给它们的根目录设置相应的安全上下文,这样以后在里面创建的文件就可以从父目录继承安全上下文了。

此外,有些文件的安全上下文是不适合使用父目录的安全上下文的,例如应用程序数据文件,它们的安全上文需要根据一定的规则来特别指定。在前面一篇文章SEAndroid安全机制框架分析中提到,SEAndroid安全机制根据应用程序类型的签名来给其数据文件设置不同的安全上下文,以区分系统应用程序和第三方应用程序的数据文件。由于无论是系统应用程序,还是第三方应用程序,它们的数据文件都是位于data分区的data子目录中的,因此我们需要有一种机制给在/data/data目录中创建的数据文件设置不同的安全上下文。我们知道,应用程序在安装的时候,PackageManagerService会通过守护进程installd在/data/data目录中创建相应的数据目录,以后应用程序在运行的过程中默认创建的数据文件就位于对应的数据目录中,因此只要给这些数据目录设置不同的安全上下文,就可以让不同类型的应用程序在运行的过程中创建不同安全上下文的数据文件。

我们通过图1总结上面描述的文件安全上下文创建方式,如下所示:

图1 文件安全上下文关联方式

接下来,我们就分别详细分析上述三种文件安全上下文关联方式。

1. 设置打包在ROM里面的文件的安全上下文

这里我们以ROM里面的system.img为例,说明打包在ROM里面的文件的安全上下文的设置过程。在前面Android系统镜像文件的打包过程分析一篇文章中,我们已经分析过system.img的制作过程了,因此这里我们只关注与安全上下文设置相关的逻辑。

生成system.img的命令位于build/core/Makefile文件中,如下所示:

BUILT_SYSTEMIMAGE := $(systemimage_intermediates)/system.img

    # $(1): output file
    define build-systemimage-target
      @echo "Target system fs image: $(1)"
      @mkdir -p $(dir $(1)) $(systemimage_intermediates) && rm -rf $(systemimage_intermediates)/system_image_info.txt
      $(call generate-userimage-prop-dictionary, $(systemimage_intermediates)/system_image_info.txt, skip_fsck=true)
      $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH \
          ./build/tools/releasetools/build_image.py \
          $(TARGET_OUT) $(systemimage_intermediates)/system_image_info.txt $(1)
    endef

    $(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)
        $(call build-systemimage-target,$@)

从这里就可以看出,system.img由命令build-system-target生成。build-system-target命令在执行的过程中,又会执行两个子命令。第一个子命令是generate-userimage-prop-dictionary,用来生成一个属性文件system_image_info.txt。第二个子命令是build_image,用来制作system.img镜像文件。注意,第二个命令在制作system.img镜像文件的过程中,会用到第一个命令生成的属性文件system_image_info.txt。

第一个子命令generate-userimage-prop-dictionary也是实现在build/core/Makefile文件中,如下所示:

SELINUX_FC := $(TARGET_ROOT_OUT)/file_contexts
    ......

    # $(1): the path of the output dictionary file
    # $(2): additional "key=value" pairs to append to the dictionary file.
    define generate-userimage-prop-dictionary
    $(if $(INTERNAL_USERIMAGES_EXT_VARIANT),$(hide) echo "fs_type=$(INTERNAL_USERIMAGES_EXT_VARIANT)" >> $(1))
    $(if $(BOARD_SYSTEMIMAGE_PARTITION_SIZE),$(hide) echo "system_size=$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)" >> $(1))
    $(if $(BOARD_USERDATAIMAGE_PARTITION_SIZE),$(hide) echo "userdata_size=$(BOARD_USERDATAIMAGE_PARTITION_SIZE)" >> $(1))
    $(if $(BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "cache_fs_type=$(BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
    $(if $(BOARD_CACHEIMAGE_PARTITION_SIZE),$(hide) echo "cache_size=$(BOARD_CACHEIMAGE_PARTITION_SIZE)" >> $(1))
    $(if $(BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "vendor_fs_type=$(BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
    $(if $(BOARD_VENDORIMAGE_PARTITION_SIZE),$(hide) echo "vendor_size=$(BOARD_VENDORIMAGE_PARTITION_SIZE)" >> $(1))
    $(if $(INTERNAL_USERIMAGES_SPARSE_EXT_FLAG),$(hide) echo "extfs_sparse_flag=$(INTERNAL_USERIMAGES_SPARSE_EXT_FLAG)" >> $(1))
    $(if $(mkyaffs2_extra_flags),$(hide) echo "mkyaffs2_extra_flags=$(mkyaffs2_extra_flags)" >> $(1))
    $(hide) echo "selinux_fc=$(SELINUX_FC)" >> $(1)

这里传过来的第一个参数便是指向上述的属性文件system_image_info.txt,它的内容是通过一系列的echo命令生成的,每一行都是"key=value"形式。其中,与文件安全上下文相关的是最后一行:

selinux_fc=$(SELINUX_FC)

变量SELINUX_FC指向一个file_contexts文件。这个file_contexts文件就是我们在前面一篇文章SEAndroid安全机制框架分析中提到的file_contexts文件,用来描述文件的安全上下文。我们知道,system.img镜像文件是安装在目标设备上的/system目录的,因此我们就观察一下在file_contexts文件中与/system目录相关的文件的安全上下文是如何设置的。

文件file_contexts最开始是位于build/external/sepolicy目录中的,经过编译后,就会保存在$OUT/root目录中,其中$OUT指向的是产品输出目录。打开$OUT/root/file_contexts文件,我们就可以看到与/system目录相关的文件的安全上下文的设置规则:

#############################
    # System files
    #
    /system(/.*)?       u:object_r:system_file:s0
    /system/bin/ash     u:object_r:shell_exec:s0
    /system/bin/mksh    u:object_r:shell_exec:s0
    /system/bin/sh      --  u:object_r:shell_exec:s0
    /system/bin/run-as  --  u:object_r:runas_exec:s0
    /system/bin/app_process u:object_r:zygote_exec:s0
    /system/bin/servicemanager  u:object_r:servicemanager_exec:s0
    /system/bin/surfaceflinger  u:object_r:surfaceflinger_exec:s0
    /system/bin/drmserver   u:object_r:drmserver_exec:s0
    /system/bin/vold    u:object_r:vold_exec:s0
    /system/bin/netd    u:object_r:netd_exec:s0
    /system/bin/rild    u:object_r:rild_exec:s0
    /system/bin/mediaserver u:object_r:mediaserver_exec:s0
    /system/bin/dbus-daemon u:object_r:dbusd_exec:s0
    /system/bin/installd    u:object_r:installd_exec:s0
    /system/bin/keystore    u:object_r:keystore_exec:s0
    /system/bin/debuggerd   u:object_r:debuggerd_exec:s0
    /system/bin/bluetoothd  u:object_r:bluetoothd_exec:s0
    /system/bin/wpa_supplicant  u:object_r:wpa_exec:s0
    /system/bin/qemud   u:object_r:qemud_exec:s0
    /system/bin/sdcard      u:object_r:sdcardd_exec:s0
    /system/bin/dhcpcd      u:object_r:dhcp_exec:s0
    /system/bin/mtpd    u:object_r:mtp_exec:s0
    /system/bin/pppd    u:object_r:ppp_exec:s0
    /system/bin/tf_daemon   u:object_r:tee_exec:s0
    /system/bin/racoon  u:object_r:racoon_exec:s0
    /system/etc/ppp(/.*)?   u:object_r:ppp_system_file:s0
    /system/etc/dhcpcd(/.*)? u:object_r:dhcp_system_file:s0
    /system/xbin/su     u:object_r:su_exec:s0
    /system/vendor/bin/gpsd u:object_r:gpsd_exec:s0
    /system/bin/ping    u:object_r:ping_exec:s0

接下来我们再来看第二个子命令build_image的实现,它是由文件build/tools/releasetools/build_image.py实现的,它的入口函数main的实现如下所示:

def main(argv):
      ......

      in_dir = argv[0]
      glob_dict_file = argv[1]
      out_file = argv[2]

      glob_dict = LoadGlobalDict(glob_dict_file)
      image_filename = os.path.basename(out_file)
      mount_point = ""
      if image_filename == "system.img":
        mount_point = "system"
      elif image_filename == "userdata.img":
        mount_point = "data"
      elif image_filename == "cache.img":
        mount_point = "cache"
      elif image_filename == "vendor.img":
        mount_point = "vendor"
      else:
        print >> sys.stderr, "error: unknown image file name ", image_filename
        exit(1)

      image_properties = ImagePropFromGlobalDict(glob_dict, mount_point)
      if not BuildImage(in_dir, image_properties, out_file):
        print >> sys.stderr, "error: failed to build %s from %s" % (out_file, in_dir)
        exit(1)

    if __name__ == '__main__':
      main(sys.argv[1:])

参数argv[1]指向的就是我们上面提到的属性文件system_image_info.txt,最终保存在本地变量glob_dict_file中。另外一个参数argv[2]指向的要输出的system.img文件路径,最终保存在本地变量out_file中。

函数LoadGlobalDict用来打开属性文件system_image_info.txt,并且将它每一行的key和value提取出来,并且保在字典glob_dict中。注意,这个字典glob_dict包含有一个key等于selinux_fc、value等于file_contexts文件路径的项。

接下来再通过os.path.basename将输出的文件路径out_file的最后一项提取出来,就可以得到image_filename的值为"system.img",因此再接下来就会得到本地变量mount_point的值为"system",表示我们现在正在打包的是system.img文件。

函数ImagePropFromGlobalDict用来从字典glob_dict中提取与安装点mount_point相关的项,并且保存在另外一个字典中返回给调用者,它的实现如下所示:

def ImagePropFromGlobalDict(glob_dict, mount_point):
      """Build an image property dictionary from the global dictionary.

      Args:
        glob_dict: the global dictionary from the build system.
        mount_point: such as "system", "data" etc.
      """
      d = {}

      def copy_prop(src_p, dest_p):
        if src_p in glob_dict:
          d[dest_p] = str(glob_dict[src_p])

      common_props = (
          "extfs_sparse_flag",
          "mkyaffs2_extra_flags",
          "selinux_fc",
          "skip_fsck",
          )
      for p in common_props:
        copy_prop(p, p)

      d["mount_point"] = mount_point
      if mount_point == "system":
        copy_prop("fs_type", "fs_type")
        copy_prop("system_size", "partition_size")
      elif mount_point == "data":
        copy_prop("fs_type", "fs_type")
        copy_prop("userdata_size", "partition_size")
      elif mount_point == "cache":
        copy_prop("cache_fs_type", "fs_type")
        copy_prop("cache_size", "partition_size")
      elif mount_point == "vendor":
        copy_prop("vendor_fs_type", "fs_type")
        copy_prop("vendor_size", "partition_size")

      return d

从这里就可以看出,函数ImagePropFromGlobalDict返回给调用者的字典包含一个以selinux_fc为key值的项,它的值指向上述分析的file_contexts文件。

回到函数main中,最后它调用另外一个函数BuildImage来生成最终的system.img文件,它的实现如下所示:

def BuildImage(in_dir, prop_dict, out_file):
      """Build an image to out_file from in_dir with property prop_dict.

      Args:
        in_dir: path of input directory.
        prop_dict: property dictionary.
        out_file: path of the output image file.

      Returns:
        True iff the image is built successfully.
      """
      build_command = []
      fs_type = prop_dict.get("fs_type", "")
      run_fsck = False
      if fs_type.startswith("ext"):
        build_command = ["mkuserimg.sh"]
        ......
        build_command.extend([in_dir, out_file, fs_type,
                              prop_dict["mount_point"]])
        ......
        if "selinux_fc" in prop_dict:
          build_command.append(prop_dict["selinux_fc"])
      else:
        build_command = ["mkyaffs2image", "-f"]
        ......
        build_command.append(in_dir)
        build_command.append(out_file)
        if "selinux_fc" in prop_dict:
          build_command.append(prop_dict["selinux_fc"])
          build_command.append(prop_dict["mount_point"])

      exit_code = RunCommand(build_command)
      ......

      return exit_code == 0

参数prop_dict指向的就是前面调用ImagePropFromGlobalDict获得的字典,如果它里面包含有一个key为"fs_type"的项,并且它的value等于"ext",那么就意味着将要制作ext格式的system.img镜像文件,否则的话,就意味着将要制作yaffs2格式的system.img镜像文件。前者通过命令mkuserimg来生成,而后者通过命令mkyaffs2image来生成。无论生成的是什么格式的system.img镜像文件, 只要参数prop_dict包含有一个key为'selinux_fc'的项,那么都会将它的value提取出来,并且作为一个参数传递给命令mkuserimg或者mkyaffs2image使用。

根据前面我们的分析,参数prop_dict描述的字典包含有key为'selinux_fc'的项,并且它的value描述的就是我们在上面提到的file_contexts的路径。当我们将这个file_contexts文件路径传递给命令mkuserimg或者mkyaffs2image时,后者就会根据它设置的规则给打包在system.img里面的文件关联安全上下文。这样我们就获得了一个关联有安全上下文的system.img镜像文件了。

2. 设置虚拟文件系统的安全上下文

这里我们以selinux虚拟文件系统的安装过程为例,说明虚拟文件系统的安全上下文的设置过程。在前面的一篇文章SEAndroid安全机制框架分析中提到,系统启动之后,会由init进程在/sys/fs/selinux中安装一个selinux虚拟文件系统,接着再加载SEAndroid安全策略到内核空间的SELinux LSM模块中去。

SEAndroid安全策略是由external/sepolicy模块生成的。观察external/sepolicy模块的编译脚本Android.mk,我们就会发现,SEAndroid安全策略包含有文件genfs_contexts定义的安全上下文设置规则,如下所示:

##################################
    include $(CLEAR_VARS)

    LOCAL_MODULE := sepolicy
    LOCAL_MODULE_CLASS := ETC
    LOCAL_MODULE_TAGS := optional
    LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)

    include $(BUILD_SYSTEM)/base_rules.mk

    sepolicy_policy.conf := $(intermediates)/policy.conf
    $(sepolicy_policy.conf): PRIVATE_MLS_SENS := $(MLS_SENS)
    $(sepolicy_policy.conf): PRIVATE_MLS_CATS := $(MLS_CATS)
    $(sepolicy_policy.conf) : $(call build_policy, security_classes initial_sids access_vectors global_macros mls_macros mls policy_capabilities te_macros attributes *.te roles users initial_sid_contexts fs_use genfs_contexts port_contexts)
        @mkdir -p $(dir $@)
        $(hide) m4 -D mls_num_sens=$(PRIVATE_MLS_SENS) -D mls_num_cats=$(PRIVATE_MLS_CATS) -s $^ > $@
        $(hide) sed '/dontaudit/d' $@ > $@.dontaudit

    $(LOCAL_BUILT_MODULE) : $(sepolicy_policy.conf) $(HOST_OUT_EXECUTABLES)/checkpolicy
        @mkdir -p $(dir $@)
        $(hide) $(HOST_OUT_EXECUTABLES)/checkpolicy -M -c $(POLICYVERS) -o $@ $<
        $(hide) $(HOST_OUT_EXECUTABLES)/checkpolicy -M -c $(POLICYVERS) -o $(dir $<)/$(notdir $@).dontaudit $<.dontaudit

    built_sepolicy := $(LOCAL_BUILT_MODULE)
    sepolicy_policy.conf :=

生成的SEAndroid安全策略保存在一个名称为policy.conf文件中。在生成这个policy.conf文件的过程中,会调用一个build_policy函数。传递给函数build_policy的参数包含了生成SEAndroid安全策略所需要的源文件。其中,源文件genfs_contexts描述的是虚拟文件系统的安全上下文设置规则,它的内容如下所示:

# Label inodes with the fs label.
    genfscon rootfs / u:object_r:rootfs:s0
    # proc labeling can be further refined (longest matching prefix).
    genfscon proc / u:object_r:proc:s0
    genfscon proc /net/xt_qtaguid/ctrl u:object_r:qtaguid_proc:s0
    # selinuxfs booleans can be individually labeled.
    genfscon selinuxfs / u:object_r:selinuxfs:s0
    genfscon cgroup / u:object_r:cgroup:s0
    # sysfs labels can be set by userspace.
    genfscon sysfs / u:object_r:sysfs:s0
    genfscon inotifyfs / u:object_r:inotify:s0
    genfscon vfat / u:object_r:sdcard_external:s0
    genfscon debugfs / u:object_r:debugfs:s0
    genfscon fuse / u:object_r:sdcard_internal:s0

从这里我们就可以看出selinux虚拟文件系统关联的安全上下文为"u:object_r:selinuxfs:s0",这意味着只有那些对Type为"selinuxfs"的文件有访问权限的进程才可以访问selinux虚拟文件系统,也就是/sys/fs/selinux目录下的文件。

3. 设置应用程序数据文件的安全上下文

在Android系统中,每一个应用程序在/data/data目录下都有一个以包名命名的目录,用来作为数据保存目录。这个数据目录是在应用程序安装的时候由守护进程installd创建的。守护进程installd在创建应用程序数据目录的时候,会同时设置它的安全上下文,以便可以对它进行保护。Android应用程序的详细安装过程可以参考前面Android应用程序安装过程源代码分析一文,这里只关注应用程序数据目录的创建及其安全上下文设置的过程。

PackageManagerService负责安装Android应用程序,它在启动的时候会通过SELinuxMMAC类的静态成员函数readInstallPolicy读取我们在前面SEAndroid安全机制框架分析一文分析的mac_permissions.xml文件,如下所示:

public class PackageManagerService extends IPackageManager.Stub {
        ......

        public PackageManagerService(Context context, Installer installer,
                boolean factoryTest, boolean onlyCore) {
            ......

            synchronized (mInstallLock) {
            // writer
            synchronized (mPackages) {
                ......

                mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();

                ......
            } // synchronized (mPackages)
            } // synchronized (mInstallLock)
        }

        ......
    }

这个函数定义在文件frameworks/base/services/java/com/android/server/pm/PackageManagerService.java文件中。

SELinuxMMAC类的静态成员函数readInstallPolicy的实现如下所示:

public final class SELinuxMMAC {
        ......

        // Signature seinfo values read from policy.
        private static final HashMap<Signature, String> sSigSeinfo =
            new HashMap<Signature, String>();

        // Package name seinfo values read from policy.
        private static final HashMap<String, String> sPackageSeinfo =
            new HashMap<String, String>();

        // Locations of potential install policy files.
        private static final File[] INSTALL_POLICY_FILE = {
            new File(Environment.getDataDirectory(), "system/mac_permissions.xml"),
            new File(Environment.getRootDirectory(), "etc/security/mac_permissions.xml"),
            null};
        ......

        public static boolean readInstallPolicy() {

            return readInstallPolicy(INSTALL_POLICY_FILE);
        }
        .......

        private static boolean readInstallPolicy(File[] policyFiles) {
            FileReader policyFile = null;
            int i = 0;
            while (policyFile == null && policyFiles != null && policyFiles[i] != null) {
                try {
                    policyFile = new FileReader(policyFiles[i]);
                    break;
                } catch (FileNotFoundException e) {
                    Slog.d(TAG,"Couldn't find install policy " + policyFiles[i].getPath());
                }
                i++;
            }

            ......

            try {
                XmlPullParser parser = Xml.newPullParser();
                parser.setInput(policyFile);

                XmlUtils.beginDocument(parser, "policy");
                while (true) {
                    XmlUtils.nextElement(parser);
                    if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
                        break;
                    }

                    String tagName = parser.getName();
                    if ("signer".equals(tagName)) {
                        String cert = parser.getAttributeValue(null, "signature");
                        ......
                        Signature signature;
                        try {
                            signature = new Signature(cert);
                        } catch (IllegalArgumentException e) {
                            ......
                        }
                        String seinfo = readSeinfoTag(parser);
                        if (seinfo != null) {
                            ......
                            sSigSeinfo.put(signature, seinfo);
                        }
                    } else if ("default".equals(tagName)) {
                        String seinfo = readSeinfoTag(parser);
                        if (seinfo != null) {
                            ......
                            // The 'null' signature is the default seinfo value
                            sSigSeinfo.put(null, seinfo);
                        }
                    } else if ("package".equals(tagName)) {
                        String pkgName = parser.getAttributeValue(null, "name");
                        ......
                        String seinfo = readSeinfoTag(parser);
                        if (seinfo != null) {
                            ......
                            sPackageSeinfo.put(pkgName, seinfo);
                        }
                    } else {
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                }
            } catch (XmlPullParserException e) {
                Slog.w(TAG, "Got execption parsing ", e);
            } catch (IOException e) {
                Slog.w(TAG, "Got execption parsing ", e);
            }
            try {
                policyFile.close();
            } catch (IOException e) {
                //omit
            }
            return true;
        }

        ......
    }

这个函数定义在文件frameworks/base/services/java/com/android/server/pm/SELinuxMMAC.java文件中。

先看SELinuxMMAC类的两个静态成员变量sSigSeinfo和sPackageSeinfo,它们指向的都是一个HashMap。其中,sSigSeinfo是以应用程序签名为Key值的,而sPackageSeinfo是以应用程序包名为Key值,并且两者的Value描述的都是一个seinfo字符串。这个seinfo字符串决定了应用程序数据目录的安全上下文设置。

再看SELinuxMMAC类的静态成员函数readInstallPolicy,这里列出了两个重载版本的实现。其中,无参数版本的readInstallPolicy以静态成员INSTALL_POLICY_FILE 描述的一个数组为参数,调用File数组参数版本的readInstallPolicy。

SELinuxMMAC类的静态成员变量INSTALL_POLICY_FILE指向的是一个File数组,它里包含了两个文件路径,分别是/data/system/mac_permissions.xml和/etc/security/mac_permissions.xml。从File数组参数版本的readInstallPolicy的开始实现可以知道,它会依次检查/data/system/mac_permissions.xml和/etc/security/mac_permissions.xml文件的存在性。只要其中的一个文件存在,那么就会停止检查过程,并且将已经存在的文件打开,以便接下来可以对它进行读取和解析。一般来说,mac_permissions.xml文件都是保存在/etc/security目录下的。

打开了要读取的解析的mac_permissions.xml文件之后,接下来就开始对它进行解析了。mac_permissions.xml是一个xml文件,它的内容可以参考前面SEAndroid安全机制框架分析一文。对mac_permissions.xml的解析是比较简单的,逻辑如下所示:

A. 如果碰到一个以"signer"为名称的Xml标签,那么就将它的signature属性值读取出来,并且以此为参数创建了一个Signature对象。接下来再通过另外一个函数readSeinfoTag在以"signer"为名称的Xml标签中,寻找一个名称为"seinfo"的Xml标签,并且将它的value属性值读取出来,作为一个seinfo字符串。最后,就以刚才创建的Signature对象为Key值,并且以通过函数readSeinfoTag读取出来的seinfo字符串为Value值,保存在SELinuxMMAC类的静态成员函数sSigSeinfo所描述的一个HashMap中。

B. 如果碰到一个以"package"为名称的Xml标签,那么就将它的name属性值读取出来,作为包名。接下来同样是通过函数readSeinfoTag在以"package"为名称的Xml标签中,寻找一个名称为"seinfo"的Xml标签,并且将它的value属性值读取出来,作为一个seinfo字符串。最后,就以刚才读取的包名为Key值,并且以通过函数readSeinfoTag读取出来的seinfo字符串为Value值,保存在SELinuxMMAC类的静态成员函数sPackageSeinfo所描述的一个HashMap中。

C. 如果碰到一个以"default"为名称的Xml标签,那么就直接通过函数readSeinfoTag在其子标签中,寻找一个名称为"seinfo"的Xml标签,并且将它的value属性值读取出来,作为一个seinfo字符串。最后,以null为Key值,并且以通过函数readSeinfoTag读取出来的seinfo字符串为Value值,保存在SELinuxMMAC类的静态成员函数sSigSeinfo所描述的一个HashMap中。

也就是说,我们可以在mac_permissions.xml文件中通过签名或者包名来为某一个应用程序设置特定的seinfo字符串,而对于没有显示通过签名或者包名来设置seinfo字符串的应用程序,那么它们的seinfo字符串就由mac_permissions.xml文件中的default标签来确定。

PackageManagerService通过调用SELinuxMMAC类的静态成员函数readInstallPolicy,就初始化好了接下来安装应用程序时要用到的seinfo字符串。

从前面Android应用程序安装过程源代码分析一文可以知道,PackageManagerService在安装应用程序的时候,会调用到PackageManagerService类的成员函数scanPackageLI来解析应用程序的信息。这个函数会同时也会给正在安装的应用程序分配一个seinfo字符串,如下所示:

public class PackageManagerService extends IPackageManager.Stub {
        ......

        private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
                int parseFlags, int scanMode, long currentTime, UserHandle user) {
            ......

            synchronized (mPackages) {
                ......

                if (mFoundPolicyFile) {
                    SELinuxMMAC.assignSeinfoValue(pkg);
                }

                ......
            }

            ......
       }

       ......
    }

这个函数定义在文件frameworks/base/services/java/com/android/server/pm/PackageManagerService.java中。

在PackageManagerService类的成员函数scanPackageLI中,参数pkg指向的是一个Package对象,用来描述正在安装的应用程序。如果PackageManagerService类的成员变量mFoundPolicyFile的值等于true,那么就说明前面已经成功地读取和解析到了一个mac_permissions.xml文件,这时候就会调用SELinuxMMAC类的静态成员函数assignSeinfoValue给正在安装的应用程序设置一个seinfo字符串。

SELinuxMMAC类的静态成员函数assignSeinfoValue的实现如下所示:

public final class SELinuxMMAC {
        ......

        public static void assignSeinfoValue(PackageParser.Package pkg) {
            ......

            if (((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) ||
                ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {

                // We just want one of the signatures to match.
                for (Signature s : pkg.mSignatures) {
                    ......

                    if (sSigSeinfo.containsKey(s)) {
                        String seinfo = pkg.applicationInfo.seinfo = sSigSeinfo.get(s);
                        ......

                        return;
                    }
                }

                // Check for seinfo labeled by package.
                if (sPackageSeinfo.containsKey(pkg.packageName)) {
                    String seinfo = pkg.applicationInfo.seinfo = sPackageSeinfo.get(pkg.packageName);
                    ......

                    return;
                }
            }

            String seinfo = pkg.applicationInfo.seinfo = sSigSeinfo.get(null);
            ......
        }
    }

这个函数定义在文件frameworks/base/services/java/com/android/server/pm/SELinuxMMAC.java文件中。

从SELinuxMMAC类的静态成员函数assignSeinfoValue的实现可以看出:

A. 对于系统应用程序来说,首先根据签名来确定它的seinfo字符串。如果不能通过签名来确定它的seinfo字符串,那就再通过包名来确定它的seinfo字符串。如果通过包名也不能确定它的seinfo字符串,那么就使用默认的seinfo字符串。

B. 对于第三方应用程序来说,它们的seinfo字符串均为默认seinfo字符串。

无论如何,最终获得的seinfo字符串都保存在用来描述应用程序安装信息的Package对象的成员变量applicationInfo所描述的一个ApplicationInfo对象的成员变量seinfo中。

当PackageManagerService解析完成要安装的应用程序的信息之后,接下来就会调用PackageManagerService类的成员函数createDataDirsLI来给正在安装的应用程序创建数据目录,如下所示:

public class PackageManagerService extends IPackageManager.Stub {
        ......

        private int createDataDirsLI(String packageName, int uid, String seinfo) {
            int[] users = sUserManager.getUserIds();
            int res = mInstaller.install(packageName, uid, uid, seinfo);
            ......
        }

        ......
    }

这个函数定义在frameworks/base/services/java/com/android/server/pm/PackageManagerService.java文件中。

参数packageName描述的是正在安装的应用程序的包名,而参数uid和seinfo描述的是分配给正在安装的应用程序的uid和seinfo。PackageManagerService类的成员函数createDataDirsLI通过调用成员变量mInstaller指向的一个Installer对象的成员函数install来为正在安装的应用程序创建数据目录,并且会将包名、uid和seinfo等信息传递给它。

Installer类的成员函数install的实现如下所示:

public final class Installer {
        ......

        public int install(String name, int uid, int gid, String seinfo) {
            StringBuilder builder = new StringBuilder("install");
            builder.append(' ');
            builder.append(name);
            builder.append(' ');
            builder.append(uid);
            builder.append(' ');
            builder.append(gid);
            builder.append(' ');
            builder.append(seinfo != null ? seinfo : "!");
            return execute(builder.toString());
        }

        ......
    }

这个函数定义在frameworks/base/services/java/com/android/server/pm/Installer.java文件中。

Installer类的成员函数install通过socket向守护进程installd发送一个install命令,请求为正在安装的应用程序创建数据目录,并且会将包名、uid和seinfo等信息传递给它。

守护进程installd的源码位于frameworks/native/cmds/installd目录中,当它收到PackageManagerService发送过来的intall命令后,就会调用里面的一个函数install来为正在安装的应用程序创建数据目录,它的实现如下所示:

int install(const char *pkgname, uid_t uid, gid_t gid, const char *seinfo)
    {
        char pkgdir[PKG_PATH_MAX];
        ......

        if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) {
            ALOGE("cannot create package path\n");
            return -1;
        }

        ......

        if (selinux_android_setfilecon2(pkgdir, pkgname, seinfo, uid) < 0) {
            ALOGE("cannot setfilecon dir '%s': %s\n", pkgdir, strerror(errno));
            ......
            return -errno;
        }

        ......

        return 0;
    }

这个函数定义在rameworks/native/cmds/installd/commands.c文件中。

函数install首先是调用另外一个函数create_pkg_path在/data/data目录下创建一个名称等于包名pkgname的子目录,接着再调用由libselinux库提供的函数selinux_android_setfilecon2为刚才创建的目录设置安全上下文。

函数selinux_android_setfilecon2的实现如下所示:

int selinux_android_setfilecon2(const char *pkgdir,
                    const char *pkgname,
                    const char *seinfo,
                    uid_t uid)
    {
        char *orig_ctx_str = NULL, *ctx_str;
        context_t ctx = NULL;
        int rc;

        if (is_selinux_enabled() <= 0)
            return 0;

        __selinux_once(once, seapp_context_init);

        rc = getfilecon(pkgdir, &ctx_str);
        if (rc < 0)
            goto err;

        ctx = context_new(ctx_str);
        orig_ctx_str = ctx_str;
        if (!ctx)
            goto oom;

        rc = seapp_context_lookup(SEAPP_TYPE, uid, 0, seinfo, pkgname, ctx);
        if (rc == -1)
            goto err;
        else if (rc == -2)
            goto oom;

        ctx_str = context_str(ctx);
        if (!ctx_str)
            goto oom;
        rc = security_check_context(ctx_str);
        if (rc < 0)
            goto err;

        if (strcmp(ctx_str, orig_ctx_str)) {
            rc = setfilecon(pkgdir, ctx_str);
            if (rc < 0)
                goto err;
        }

        rc = 0;
    out:
        freecon(orig_ctx_str);
        context_free(ctx);
        return rc;
    err:
        selinux_log(SELINUX_ERROR, "%s:  Error setting context for pkgdir %s, uid %d: %s\n",
                __FUNCTION__, pkgdir, uid, strerror(errno));
        rc = -1;
        goto out;
    oom:
        selinux_log(SELINUX_ERROR, "%s:  Out of memory\n", __FUNCTION__);
        rc = -1;
        goto out;
    }

这个函数定义在external/libselinux/src/android.c文件中。

函数selinux_android_setfilecon2的实现逻辑如下所示:

A. 调用函数is_selinux_enabled检查系统是否启用了SELinux。如果没有启用的话,那就什么也不用做就返回。否则的话,就继续往下执行。

B. 调用函数seapp_context_init读取和解析我们在前面SEAndroid安全机制框架分析一文提到的seapp_contexts文件。注意,__selinux_once是一个宏,它实际上是利用了pthread库提供的函数pthread_once保证函数seapp_context_init在进程内有且仅有一次会被调用到,适合用来执行初始化工作。

C. 调用函数getfilecon获得要设置新的安全上下文的目录pkgdir原来已有的安全上下文,保存在变量ctx_str。我们在SEAndroid安全机制框架分析一文提到,文件或者目录在创建的时候,默认设置的是父目录的安全上下文。

D. 调用函数context_new在原来的安全上下文的基础上创建一个新的安全上下文ctx,以便可以获得原来安全上下文的SELinux用户、角色和安全级别等信息。

E. 调用函数seapp_context_lookup根据传进来的参数seinfo在seapp_contexts文件中找到对应的Type,并且将其设置为新的安全上下文ctr的Type。

F. 调用函数context_str获得新创建的安全上下文的字符串描述,以便可以调用函数security_check_context来验证新创建的安全上下文的正确性。如果不正确的话,就出错返回。否则的话,继续往下执行。

G. 比较原来的安全上下文和新创建的安全上下文。如果不一致的话,那么就调用函数setfilecon将目录pkgdir的安全上下文设置为新创建的安全上下文。

在上面几个步骤中,最重要的就是B、E和G三步,因此接下来我们就继续分析函数seapp_context_init、seapp_context_lookup和setfilecon的实现。

函数seapp_context_init实现在external/libselinux/src/android.c文件中。如下所示:

static void seapp_context_init(void)
    {
            selinux_android_seapp_context_reload();
    }

它通过调用另外一个函数selinux_android_seapp_context_reload来读取和解析seapp_contexts文件。

函数selinux_android_seapp_context_reload也是实现在external/libselinux/src/android.c文件中。如下所示:

int selinux_android_seapp_context_reload(void)
    {
        FILE *fp = NULL;
        char line_buf[BUFSIZ];
        ......
        struct seapp_context *cur;
        ......

        while ((fp==NULL) && seapp_contexts_file[i])
            fp = fopen(seapp_contexts_file[i++], "r");  
        ......

        seapp_contexts = calloc(nspec, sizeof(struct seapp_context *));
        ......

        while (fgets(line_buf, sizeof line_buf - 1, fp)) {
            ......

            cur = calloc(1, sizeof(struct seapp_context));
            ......

            token = strtok_r(p, " \t", &saveptr);
            ......

            while (1) {
                name = token;
                value = strchr(name, '=');
                ......

                if (!strcasecmp(name, "isSystemServer")) {
                    if (!strcasecmp(value, "true"))
                        cur->isSystemServer = 1;
                    else if (!strcasecmp(value, "false"))
                        cur->isSystemServer = 0;
                    ......
                } else if (!strcasecmp(name, "user")) {
                    cur->user = strdup(value);
                    ......
                } else if (!strcasecmp(name, "seinfo")) {
                    cur->seinfo = strdup(value);
                    ......
                } else if (!strcasecmp(name, "name")) {
                    cur->name = strdup(value);
                    ......
                } else if (!strcasecmp(name, "domain")) {
                    cur->domain = strdup(value);
                    ......
                } else if (!strcasecmp(name, "type")) {
                    cur->type = strdup(value);
                    ......
                } else if (!strcasecmp(name, "levelFromUid")) {
                    if (!strcasecmp(value, "true"))
                        cur->levelFrom = LEVELFROM_APP;
                    else if (!strcasecmp(value, "false"))
                        cur->levelFrom = LEVELFROM_NONE;
                    ......
                } else if (!strcasecmp(name, "levelFrom")) {
                    if (!strcasecmp(value, "none"))
                        cur->levelFrom = LEVELFROM_NONE;
                    else if (!strcasecmp(value, "app"))
                        cur->levelFrom = LEVELFROM_APP;
                    else if (!strcasecmp(value, "user"))
                        cur->levelFrom = LEVELFROM_USER;
                    else if (!strcasecmp(value, "all"))
                        cur->levelFrom = LEVELFROM_ALL;
                    .......
                } else if (!strcasecmp(name, "level")) {
                    cur->level = strdup(value);
                    ......
                } else if (!strcasecmp(name, "sebool")) {
                    cur->sebool = strdup(value);
                    ......
                }
                ......

                token = strtok_r(NULL, " \t", &saveptr);
                ......
            }

            seapp_contexts[nspec] = cur;
            ......
        }

        ......

        ret = 0;
    out:
        fclose(fp);
        return ret;

        ......
    }

seapp_contexts_file是一个全局变量,也是定义在external/libselinux/src/android.c文件中。如下所示:

static char const * const seapp_contexts_file[] = {
        "/data/security/current/seapp_contexts",
        "/seapp_contexts",
        0 };

从这里我们就可以看到,seapp_contexts可能保存在目录/data/security/current中,也有可能保存在根目录中,函数selinux_android_seapp_context_reload依次进行检查。只要其中一个地方存在,那么就对它进行打开。打开之后,接下来就按行进行解析。

文件seapp_contexts的内容如下所示:

isSystemServer=true domain=system
    user=system domain=system_app type=system_data_file
    user=bluetooth domain=bluetooth type=bluetooth_data_file
    user=nfc domain=nfc type=nfc_data_file
    user=radio domain=radio type=radio_data_file
    user=_app domain=untrusted_app type=app_data_file levelFrom=none
    user=_app seinfo=platform domain=platform_app type=platform_app_data_file
    user=_app seinfo=shared domain=shared_app type=platform_app_data_file
    user=_app seinfo=media domain=media_app type=platform_app_data_file
    user=_app seinfo=release domain=release_app type=platform_app_data_file
    user=_isolated domain=isolated_app

函数selinux_android_seapp_context_reload将每一行的内容都用一个seapp_context结构体来描述,并且将这些结构体全部缓存在全局变量seapp_contexts之中。通过这种方式,以后需要从文件seapp_contexts中查找文件的Type或者进程的Domain时,就可以直接在内存进行,加快了查找的速度。

函数seapp_context_lookup就是用来从全局变量seapp_contexts中查找文件的Type或者进程的Domain,它定义在external/libselinux/src/android.c文件中。如下所示:

static int seapp_context_lookup(enum seapp_kind kind,
                    uid_t uid,
                    int isSystemServer,
                    const char *seinfo,
                    const char *pkgname,
                    context_t ctx)
    {
        const char *username = NULL;
        ......
        struct seapp_context *cur;
        ......

        userid = uid / AID_USER;
        appid = uid % AID_USER;
        if (appid < AID_APP) {
            for (n = 0; n < android_id_count; n++) {
                if (android_ids[n].aid == appid) {
                    username = android_ids[n].name;
                    break;
                }
            }
            ......
        } else if (appid < AID_ISOLATED_START) {
            username = "_app";
            ......
        } else {
            username = "_isolated";
            ......
        }

        ......

        for (i = 0; i < nspec; i++) {
            cur = seapp_contexts[i];

            if (cur->isSystemServer != isSystemServer)
                continue;

            if (cur->user) {
                if (cur->prefix) {
                    if (strncasecmp(username, cur->user, cur->len-1))
                        continue;
                } else {
                    if (strcasecmp(username, cur->user))
                        continue;
                }
            }

            if (cur->seinfo) {
                if (!seinfo || strcasecmp(seinfo, cur->seinfo))
                    continue;
            }

            if (cur->name) {
                if (!pkgname || strcasecmp(pkgname, cur->name))
                    continue;
            }

            if (kind == SEAPP_TYPE && !cur->type)
                continue;
            else if (kind == SEAPP_DOMAIN && !cur->domain)
                continue;

            if (cur->sebool) {
                int value = security_get_boolean_active(cur->sebool);
                if (value == 0)
                    continue;
                ......
            }

            if (kind == SEAPP_TYPE) {
                if (context_type_set(ctx, cur->type))
                    goto oom;
            } else if (kind == SEAPP_DOMAIN) {
                if (context_type_set(ctx, cur->domain))
                    goto oom;
            }

            ......

            break;
        }

        ......

        return 0;

        ......
    }

函数seapp_context_lookup首先根据参数uid来确定接下来的查找中要用到的关键字username,这对应于seapp_contexts文件中的user字段。

Android从4.2开始支持多用户。支持多用户之后,应用程序在安装的时候分配到的UID由两部分组成,一部分是User Id,另一部分是App Id。常量AID_USER的值定义为100000,用UID除以该值获得的整数就为User Id,而用UID对该值进行取模得到的就为App Id。

App Id小于AID_APP(10000)是保留给系统使用的,它们对应的username保存在数组android_ids中,具体可以参考文件system/core/include/private/android_filesystem_config.h。

App Id大于等于AID_APP(10000)并且小于AID_ISOLATED_START(99000)是分配给应用程序使用的,它们对应的username统一设置为"_app"。

App Id大于等于AID_ISOLATED_START(99000)是分配给运行在独立沙箱(没有任何自己的Permission)的应用程序使用的,它们对应的username统一设置为"_isolated"。

接下来就根据username、isSystemServer、seinfo和pkgname等查找关键字在全局变量seapp_contexts中寻找目标文件的Type或者目标进程的Domain,这是由参数kind的值决定的。如果kind的值等于SEAPP_TYPE,那么就表明要寻找的是文件的Type。如果kind的值等于SEAPP_DOMAIN,那么就表明要查找的是文件的Domain。在我们这个场景中,我们要查找的是文件的Type。在接下来的一篇文章分析进程的安全上下文时,我们就会通过函数seapp_context_lookup查找目标进程的Domain。

查找的过程是相当直觉的。通过遍历保存在数组seapp_contexts中的每一个seapp_context,如果该seapp_context定义了相应的字段,那么就要求它与对应的查找关键字相等。如果某一个字段与对应的查找关键字不相等,就说明当前遍历的seapp_context不匹配,要继续下一个seapp_context的查找。有一点需要特别注意的是,如果当前正在遍历的seapp_context的某一个字段没有定义,那么它就默认匹配对应的查找关键字。

最后,如果查找到了匹配的seapp_context,那么就将它的Type或者Domain字段值提取出来,并且设置到参数ctx所描述的安全上下文中去,以便返回给调用者使用。

我们再来看函数setfilecon的实现,它定义在文件external/libselinux/src/setfilecon.c中,如下所示:

int setfilecon(const char *path, const security_context_t context)
    {
        return setxattr(path, XATTR_NAME_SELINUX, context, strlen(context) + 1,
                0);
    }

参数path描述的就是要设置安全上下文的目录或者文件,而参数context描述的就是要设置的安全上下文。

函数setfilecon实际上是通过调用系统接口setxattr将参数context描述的安全上下文设置为参数path描述的目录或者文件名称为XATTR_NAME_SELINUX("security.selinux")的扩展属性中。也就是说,在SEAndroid中,目录或者文件的安全上下文其实是保存在名称为security.selinux扩展属性中的。这要求对应的文件系统支持扩展属性。

至此,我们就分析完成在SEAndroid安全机制中,文件安全上下文的关联过程了。在接下来的一篇文章中,我们将继续分析SEAndroid安全机制中的进程安全上下文的关联过程了。当进程和文件的安全上下文都设置好之后,SEAndroid安全策略就有用武之地了。敬请关注!更多信息可以关注老罗的新浪微博:http://weibo.com/shengyangluo

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 目录